"""
Humidity monitoring and analysis for cave systems.
Tracks relative humidity, condensation patterns, and microclimate changes.

Author: Samir Baladi
"""

import numpy as np
from typing import Dict, List, Tuple, Optional, Union
from dataclasses import dataclass
import logging

logger = logging.getLogger(__name__)

@dataclass
class HumidityReading:
    """Represents a humidity reading."""
    timestamp: float
    relative_humidity: float  # %
    temperature: Optional[float]  # °C, for dew point calculation
    location: str
    sensor_id: str


class HumidityMonitor:
    """
    Monitors and analyzes cave humidity.
    """
    
    def __init__(self):
        """Initialize humidity monitor."""
        self.readings = []
        self.sensors = {}
        logger.info("Initialized HumidityMonitor")

    def add_reading(self,
                   timestamp: float,
                   relative_humidity: float,
                   location: str,
                   sensor_id: str,
                   temperature: Optional[float] = None) -> None:
        """
        Add a humidity reading.
        
        Args:
            timestamp: Reading time
            relative_humidity: Relative humidity (%)
            location: Measurement location
            sensor_id: Sensor identifier
            temperature: Optional temperature for dew point
        """
        reading = HumidityReading(
            timestamp=timestamp,
            relative_humidity=relative_humidity,
            temperature=temperature,
            location=location,
            sensor_id=sensor_id
        )
        
        self.readings.append(reading)
        
        # Update sensor info
        if sensor_id not in self.sensors:
            self.sensors[sensor_id] = {
                'location': location,
                'first_reading': timestamp,
                'n_readings': 0
            }
        self.sensors[sensor_id]['n_readings'] += 1
        
        logger.debug(f"Added humidity reading: {relative_humidity}% at {location}")

    def calculate_statistics(self,
                            location: Optional[str] = None,
                            start_time: Optional[float] = None,
                            end_time: Optional[float] = None) -> Dict[str, float]:
        """
        Calculate humidity statistics.
        
        Args:
            location: Filter by location
            start_time: Start time
            end_time: End time
            
        Returns:
            Dictionary with statistics
        """
        readings = self._filter_readings(location, start_time, end_time)
        
        if not readings:
            return {}
        
        values = [r.relative_humidity for r in readings]
        
        return {
            'mean': round(np.mean(values), 1),
            'std': round(np.std(values), 1),
            'min': round(np.min(values), 1),
            'max': round(np.max(values), 1),
            'median': round(np.median(values), 1),
            'range': round(np.max(values) - np.min(values), 1),
            'n_readings': len(readings)
        }

    def calculate_dew_point(self,
                           temperature: float,
                           relative_humidity: float) -> float:
        """
        Calculate dew point temperature.
        
        Args:
            temperature: Air temperature (°C)
            relative_humidity: Relative humidity (%)
            
        Returns:
            Dew point temperature (°C)
        """
        # Magnus formula
        a = 17.27
        b = 237.7
        
        alpha = ((a * temperature) / (b + temperature)) + np.log(relative_humidity / 100.0)
        dew_point = (b * alpha) / (a - alpha)
        
        return round(dew_point, 1)

    def detect_condensation_risk(self,
                                temperature: float,
                                relative_humidity: float,
                                wall_temperature: Optional[float] = None) -> Dict[str, Union[str, float]]:
        """
        Detect risk of condensation.
        
        Args:
            temperature: Air temperature (°C)
            relative_humidity: Relative humidity (%)
            wall_temperature: Wall temperature (°C), assumes air temp if None
            
        Returns:
            Dictionary with condensation risk assessment
        """
        dew_point = self.calculate_dew_point(temperature, relative_humidity)
        
        if wall_temperature is None:
            wall_temperature = temperature - 0.5  # Assume walls slightly cooler
        
        temp_diff = wall_temperature - dew_point
        
        if temp_diff <= 0:
            risk = "CONDENSATION ACTIVE"
            color = "🔴"
            description = "Active condensation occurring"
        elif temp_diff < 1:
            risk = "HIGH RISK"
            color = "🟠"
            description = "Close to condensation point"
        elif temp_diff < 2:
            risk = "MODERATE RISK"
            color = "🟡"
            description = "Possible condensation"
        elif temp_diff < 3:
            risk = "LOW RISK"
            color = "🟢"
            description = "Unlikely to condense"
        else:
            risk = "NO RISK"
            color = "🟢"
            description = "No condensation expected"
        
        return {
            'risk_level': risk,
            'color': color,
            'description': description,
            'dew_point': dew_point,
            'wall_temperature': wall_temperature,
            'temperature_difference': round(temp_diff, 1),
            'relative_humidity': relative_humidity
        }

    def analyze_humidity_trend(self,
                              location: str,
                              hours: int = 24) -> Dict[str, Union[str, float]]:
        """
        Analyze humidity trend over time.
        
        Args:
            location: Location to analyze
            hours: Time window in hours
            
        Returns:
            Dictionary with trend analysis
        """
        readings = [r for r in self.readings if r.location == location]
        
        if len(readings) < 3:
            return {'status': 'Insufficient data'}
        
        # Sort by time
        readings.sort(key=lambda x: x.timestamp)
        
        # Get recent readings
        if hours > 0:
            latest_time = readings[-1].timestamp
            cutoff = latest_time - hours * 3600
            recent = [r for r in readings if r.timestamp >= cutoff]
        else:
            recent = readings[-10:]
        
        if len(recent) < 3:
            recent = readings[-3:]
        
        times = np.array([r.timestamp for r in recent])
        values = np.array([r.relative_humidity for r in recent])
        
        # Calculate trend
        if len(times) > 1:
            coeffs = np.polyfit(times - times[0], values, 1)
            slope = coeffs[0] * 3600  # % per hour
        else:
            slope = 0
        
        if slope > 1:
            trend = "RAPIDLY INCREASING"
            color = "🔴"
        elif slope > 0.2:
            trend = "INCREASING"
            color = "🟡"
        elif slope < -1:
            trend = "RAPIDLY DECREASING"
            color = "🔴"
        elif slope < -0.2:
            trend = "DECREASING"
            color = "🟡"
        else:
            trend = "STABLE"
            color = "🟢"
        
        return {
            'trend': trend,
            'color': color,
            'slope_per_hour': round(slope, 2),
            'current': round(values[-1], 1),
            'mean': round(np.mean(values), 1),
            'min': round(min(values), 1),
            'max': round(max(values), 1),
            'n_readings': len(recent)
        }

    def check_humidity_stability(self,
                                location: str,
                                threshold_std: float = 2.0) -> Dict[str, Union[str, float]]:
        """
        Check humidity stability at a location.
        
        Args:
            location: Location to check
            threshold_std: Standard deviation threshold for stability
            
        Returns:
            Dictionary with stability assessment
        """
        readings = [r for r in self.readings if r.location == location]
        
        if len(readings) < 5:
            return {'status': 'Insufficient data'}
        
        values = [r.relative_humidity for r in readings]
        std = np.std(values)
        
        if std < 1:
            stability = "VERY STABLE"
            color = "🟢"
        elif std < 2:
            stability = "STABLE"
            color = "🟢"
        elif std < 3:
            stability = "MODERATE"
            color = "🟡"
        elif std < 5:
            stability = "UNSTABLE"
            color = "🟠"
        else:
            stability = "VERY UNSTABLE"
            color = "🔴"
        
        return {
            'stability': stability,
            'color': color,
            'std_dev': round(std, 1),
            'mean': round(np.mean(values), 1),
            'range': round(np.max(values) - np.min(values), 1),
            'n_readings': len(readings)
        }

    def calculate_drying_potential(self,
                                  temperature: float,
                                  relative_humidity: float,
                                  airflow: float = 0.1) -> float:
        """
        Calculate potential for drying (evaporation).
        
        Args:
            temperature: Air temperature (°C)
            relative_humidity: Relative humidity (%)
            airflow: Airflow velocity (m/s)
            
        Returns:
            Drying potential index (0-1)
        """
        # Vapor pressure deficit
        # Saturation vapor pressure (hPa)
        es = 6.112 * np.exp((17.67 * temperature) / (temperature + 243.5))
        
        # Actual vapor pressure
        ea = es * (relative_humidity / 100)
        
        # Vapor pressure deficit
        vpd = es - ea
        
        # Normalize to 0-1
        max_vpd = 50  # Maximum expected VPD
        vpd_index = min(1, vpd / max_vpd)
        
        # Airflow factor
        airflow_factor = min(1, airflow / 0.5)
        
        # Combined drying potential
        drying_potential = vpd_index * (0.7 + 0.3 * airflow_factor)
        
        return round(drying_potential, 2)

    def _filter_readings(self,
                        location: Optional[str] = None,
                        start_time: Optional[float] = None,
                        end_time: Optional[float] = None) -> List[HumidityReading]:
        """Filter readings by criteria."""
        filtered = self.readings
        
        if location:
            filtered = [r for r in filtered if r.location == location]
        
        if start_time:
            filtered = [r for r in filtered if r.timestamp >= start_time]
        
        if end_time:
            filtered = [r for r in filtered if r.timestamp <= end_time]
        
        return filtered

    def summary(self) -> Dict[str, Union[int, float, str, Dict]]:
        """Get summary of humidity monitoring."""
        if not self.readings:
            return {'status': 'No data'}
        
        overall_stats = self.calculate_statistics()
        
        # Get latest readings by location
        latest_by_location = {}
        locations = set(r.location for r in self.readings)
        for loc in locations:
            loc_readings = [r for r in self.readings if r.location == loc]
            if loc_readings:
                latest = max(loc_readings, key=lambda x: x.timestamp)
                latest_by_location[loc] = {
                    'humidity': latest.relative_humidity,
                    'time': latest.timestamp
                }
        
        return {
            'n_readings': len(self.readings),
            'n_locations': len(locations),
            'n_sensors': len(self.sensors),
            'overall_stats': overall_stats,
            'latest_by_location': latest_by_location
        }


# Example usage
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    
    print("="*60)
    print("CAVORA - Humidity Monitoring")
    print("="*60)
    
    hm = HumidityMonitor()
    
    # Add readings
    import time
    current = time.time()
    for i in range(10):
        hm.add_reading(current + i*3600, 95 + np.random.randn()*2, 
                      "Main Passage", "H001", temperature=11.5)
    
    # Calculate statistics
    stats = hm.calculate_statistics("Main Passage")
    print(f"\nHumidity Statistics:")
    for key, value in stats.items():
        print(f"  {key}: {value}")
    
    # Check condensation risk
    risk = hm.detect_condensation_risk(temperature=11.5, relative_humidity=96)
    print(f"\nCondensation Risk:")
    print(f"  {risk['color']} {risk['risk_level']}")
    print(f"  {risk['description']}")
    
    # Analyze trend
    trend = hm.analyze_humidity_trend("Main Passage")
    print(f"\nHumidity Trend:")
    print(f"  {trend['color']} {trend['trend']}")
    print(f"  Rate: {trend['slope_per_hour']}%/hour")
    
    print("\n" + "="*60)
