"""
Temperature monitoring and analysis for cave systems.
Tracks temperature trends, gradients, and anomalies.

Author: Samir Baladi
"""

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

logger = logging.getLogger(__name__)

@dataclass
class TemperatureReading:
    """Represents a temperature reading."""
    timestamp: float
    value: float  # °C
    location: str
    depth: float  # meters below surface
    sensor_id: str


class TemperatureMonitor:
    """
    Monitors and analyzes cave temperatures.
    """
    
    def __init__(self, reference_temp: float = 11.5):
        """
        Initialize temperature monitor.
        
        Args:
            reference_temp: Reference cave temperature (°C)
        """
        self.reference_temp = reference_temp
        self.readings = []
        self.sensors = {}
        logger.info(f"Initialized TemperatureMonitor with reference {reference_temp}°C")

    def add_reading(self,
                   timestamp: float,
                   value: float,
                   location: str,
                   depth: float,
                   sensor_id: str) -> None:
        """
        Add a temperature reading.
        
        Args:
            timestamp: Reading time
            value: Temperature (°C)
            location: Measurement location
            depth: Depth below surface (m)
            sensor_id: Sensor identifier
        """
        reading = TemperatureReading(
            timestamp=timestamp,
            value=value,
            location=location,
            depth=depth,
            sensor_id=sensor_id
        )
        
        self.readings.append(reading)
        
        # Update sensor info
        if sensor_id not in self.sensors:
            self.sensors[sensor_id] = {
                'location': location,
                'depth': depth,
                'first_reading': timestamp,
                'n_readings': 0
            }
        self.sensors[sensor_id]['n_readings'] += 1
        
        logger.debug(f"Added temperature reading: {value}°C at {location}")

    def calculate_statistics(self,
                            location: Optional[str] = None,
                            start_time: Optional[float] = None,
                            end_time: Optional[float] = None) -> Dict[str, float]:
        """
        Calculate temperature 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.value for r in readings]
        
        return {
            'mean': round(np.mean(values), 2),
            'std': round(np.std(values), 2),
            'min': round(np.min(values), 2),
            'max': round(np.max(values), 2),
            'median': round(np.median(values), 2),
            'range': round(np.max(values) - np.min(values), 2),
            'n_readings': len(readings)
        }

    def calculate_thermal_gradient(self,
                                  depth1: float,
                                  depth2: float,
                                  temp1: float,
                                  temp2: float) -> float:
        """
        Calculate thermal gradient between two depths.
        
        Args:
            depth1, depth2: Depths (m)
            temp1, temp2: Temperatures (°C)
            
        Returns:
            Thermal gradient (°C/100m)
        """
        depth_diff = abs(depth2 - depth1)
        if depth_diff < 1:
            return 0
        
        temp_diff = temp2 - temp1
        gradient = (temp_diff / depth_diff) * 100  # °C per 100m
        
        return round(gradient, 2)

    def detect_temperature_anomaly(self,
                                  value: float,
                                  location: str,
                                  threshold_std: float = 2.0) -> Dict[str, Union[bool, float]]:
        """
        Detect if temperature is anomalous.
        
        Args:
            value: Current temperature
            location: Location
            threshold_std: Standard deviation threshold
            
        Returns:
            Dictionary with anomaly detection
        """
        # Get historical readings for this location
        location_readings = [r for r in self.readings if r.location == location]
        
        if len(location_readings) < 5:
            return {
                'is_anomaly': False,
                'reason': 'Insufficient historical data'
            }
        
        values = [r.value for r in location_readings]
        mean = np.mean(values)
        std = np.std(values)
        
        z_score = abs(value - mean) / std if std > 0 else 0
        is_anomaly = z_score > threshold_std
        
        return {
            'is_anomaly': is_anomaly,
            'z_score': round(z_score, 2),
            'expected': round(mean, 2),
            'std': round(std, 2),
            'current': value,
            'threshold': threshold_std
        }

    def analyze_seasonal_variation(self,
                                  location: str,
                                  years: int = 1) -> Dict[str, float]:
        """
        Analyze seasonal temperature variation.
        
        Args:
            location: Location to analyze
            years: Number of years of data
            
        Returns:
            Dictionary with seasonal analysis
        """
        readings = [r for r in self.readings if r.location == location]
        
        if len(readings) < 10:
            return {'status': 'Insufficient data'}
        
        # Extract timestamps and values
        times = np.array([r.timestamp for r in readings])
        values = np.array([r.value for r in readings])
        
        # Sort by time
        idx = np.argsort(times)
        times = times[idx]
        values = values[idx]
        
        # Detrend
        from scipy import signal
        
        # Find dominant frequency
        if len(times) > 10:
            # Assume annual cycle
            annual_amplitude = np.std(values) * np.sqrt(2)
            
            # Calculate seasonal amplitude
            seasonal_amplitude = annual_amplitude
            
            # Phase (timing of maximum)
            # Simplified - find time of max in average year
            month_of_max = 7  # July in Northern Hemisphere
        else:
            seasonal_amplitude = 0
            month_of_max = 0
        
        return {
            'seasonal_amplitude': round(seasonal_amplitude, 2),
            'annual_mean': round(np.mean(values), 2),
            'summer_mean': round(np.mean(values[6:9]) if len(values) > 9 else 0, 2),
            'winter_mean': round(np.mean(values[0:3]) if len(values) > 3 else 0, 2),
            'max_month': month_of_max,
            'variation_range': round(np.max(values) - np.min(values), 2)
        }

    def calculate_geothermal_gradient(self,
                                     min_readings: int = 5) -> Dict[str, float]:
        """
        Calculate geothermal gradient from depth profiles.
        
        Args:
            min_readings: Minimum readings per depth required
            
        Returns:
            Dictionary with gradient analysis
        """
        # Group readings by depth
        depth_readings = {}
        for r in self.readings:
            depth = round(r.depth, -1)  # Round to nearest 10m
            if depth not in depth_readings:
                depth_readings[depth] = []
            depth_readings[depth].append(r.value)
        
        # Calculate mean temperature at each depth
        depths = []
        temps = []
        for depth, values in depth_readings.items():
            if len(values) >= min_readings:
                depths.append(depth)
                temps.append(np.mean(values))
        
        if len(depths) < 2:
            return {'status': 'Insufficient depth profile data'}
        
        # Sort by depth
        depths, temps = zip(*sorted(zip(depths, temps)))
        
        # Linear regression for gradient
        coeffs = np.polyfit(depths, temps, 1)
        gradient = coeffs[0] * 100  # °C per 100m
        
        return {
            'geothermal_gradient': round(gradient, 2),
            'surface_temp': round(coeffs[1], 2),
            'correlation': round(np.corrcoef(depths, temps)[0, 1], 2),
            'n_depths': len(depths)
        }

    def check_thermal_stability(self,
                               location: str,
                               window_days: int = 30) -> Dict[str, Union[str, float]]:
        """
        Check thermal stability at a location.
        
        Args:
            location: Location to check
            window_days: Time window for stability check
            
        Returns:
            Dictionary with stability assessment
        """
        readings = [r for r in self.readings if r.location == location]
        
        if len(readings) < 5:
            return {'status': 'Insufficient data'}
        
        # Sort by time
        readings.sort(key=lambda x: x.timestamp)
        
        # Get recent readings
        if window_days > 0:
            latest_time = readings[-1].timestamp
            cutoff = latest_time - window_days * 86400
            recent = [r for r in readings if r.timestamp >= cutoff]
        else:
            recent = readings[-min(10, len(readings)):]
        
        if len(recent) < 3:
            recent = readings[-3:]
        
        recent_values = [r.value for r in recent]
        variation = np.std(recent_values)
        
        if variation < 0.1:
            stability = "VERY STABLE"
            color = "🟢"
        elif variation < 0.3:
            stability = "STABLE"
            color = "🟢"
        elif variation < 0.5:
            stability = "MODERATE"
            color = "🟡"
        elif variation < 1.0:
            stability = "UNSTABLE"
            color = "🟠"
        else:
            stability = "VERY UNSTABLE"
            color = "🔴"
        
        return {
            'stability': stability,
            'color': color,
            'variation_std': round(variation, 2),
            'mean_temp': round(np.mean(recent_values), 2),
            'min_temp': round(min(recent_values), 2),
            'max_temp': round(max(recent_values), 2),
            'n_readings': len(recent)
        }

    def _filter_readings(self,
                        location: Optional[str] = None,
                        start_time: Optional[float] = None,
                        end_time: Optional[float] = None) -> List[TemperatureReading]:
        """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 temperature 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] = {
                    'temp': latest.value,
                    '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,
            'reference_temp': self.reference_temp,
            'geothermal_gradient': self.calculate_geothermal_gradient()
        }


# Example usage
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    
    print("="*60)
    print("CAVORA - Temperature Monitoring")
    print("="*60)
    
    tm = TemperatureMonitor()
    
    # Add readings
    import time
    current = time.time()
    for i in range(10):
        tm.add_reading(current + i*86400, 11.5 + np.sin(i*0.5)*0.2, 
                      "Main Passage", 50, "T001")
    
    # Calculate statistics
    stats = tm.calculate_statistics("Main Passage")
    print(f"\nTemperature Statistics:")
    for key, value in stats.items():
        print(f"  {key}: {value}")
    
    # Check stability
    stability = tm.check_thermal_stability("Main Passage")
    print(f"\nThermal Stability:")
    print(f"  {stability['color']} {stability['stability']}")
    print(f"  Variation: ±{stability['variation_std']}°C")
    
    print("\n" + "="*60)
