"""
Water level monitoring and analysis for cave systems.
Tracks water table fluctuations, flood events, and sump formation.

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 WaterLevelReading:
    """Represents a water level reading."""
    timestamp: float
    level: float  # meters above datum
    discharge: Optional[float]  # m³/s
    location: str
    sensor_id: str


class WaterLevelMonitor:
    """
    Monitors and analyzes cave water levels.
    """
    
    def __init__(self, datum: float = 0):
        """
        Initialize water level monitor.
        
        Args:
            datum: Reference datum (m)
        """
        self.datum = datum
        self.readings = []
        self.sensors = {}
        self.flood_events = []
        logger.info(f"Initialized WaterLevelMonitor with datum={datum}m")

    def add_reading(self,
                   timestamp: float,
                   level: float,
                   location: str,
                   sensor_id: str,
                   discharge: Optional[float] = None) -> None:
        """
        Add a water level reading.
        
        Args:
            timestamp: Reading time
            level: Water level (m)
            location: Measurement location
            sensor_id: Sensor identifier
            discharge: Optional discharge measurement
        """
        reading = WaterLevelReading(
            timestamp=timestamp,
            level=level,
            discharge=discharge,
            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 water level reading: {level}m at {location}")

    def calculate_statistics(self,
                            location: Optional[str] = None,
                            start_time: Optional[float] = None,
                            end_time: Optional[float] = None) -> Dict[str, float]:
        """
        Calculate water level 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.level 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 detect_flood_events(self,
                           location: str,
                           threshold: Optional[float] = None,
                           min_duration: float = 300) -> List[Dict]:
        """
        Detect flood events (water level excursions).
        
        Args:
            location: Location to analyze
            threshold: Threshold level (m), uses 90th percentile if None
            min_duration: Minimum duration in seconds
            
        Returns:
            List of detected flood events
        """
        readings = [r for r in self.readings if r.location == location]
        
        if len(readings) < 10:
            return []
        
        # Sort by time
        readings.sort(key=lambda x: x.timestamp)
        
        if threshold is None:
            # Use 90th percentile as threshold
            values = [r.level for r in readings]
            threshold = np.percentile(values, 90)
        
        events = []
        in_event = False
        event_start = None
        event_levels = []
        event_times = []
        
        for reading in readings:
            if reading.level > threshold and not in_event:
                # Start of event
                in_event = True
                event_start = reading.timestamp
                event_levels = [reading.level]
                event_times = [reading.timestamp]
            elif reading.level > threshold and in_event:
                # Continue event
                event_levels.append(reading.level)
                event_times.append(reading.timestamp)
            elif reading.level <= threshold and in_event:
                # End of event
                event_end = reading.timestamp
                duration = event_end - event_start
                
                if duration >= min_duration:
                    # Calculate rise rate
                    if len(event_times) > 1:
                        rates = np.gradient(event_levels, event_times)
                        max_rise_rate = max(rates) * 3600  # m per hour
                    else:
                        max_rise_rate = 0
                    
                    events.append({
                        'start_time': event_start,
                        'end_time': event_end,
                        'duration_min': round(duration / 60, 1),
                        'peak_level': max(event_levels),
                        'rise_from': event_levels[0],
                        'rise_m': round(max(event_levels) - event_levels[0], 2),
                        'max_rise_rate_m_per_hour': round(max_rise_rate, 2),
                        'location': location
                    })
                
                in_event = False
                event_levels = []
                event_times = []
        
        self.flood_events.extend(events)
        return events

    def calculate_rise_rate(self,
                           location: str,
                           window_minutes: int = 60) -> Dict[str, float]:
        """
        Calculate water level rise rate.
        
        Args:
            location: Location to analyze
            window_minutes: Time window in minutes
            
        Returns:
            Dictionary with rise rate calculations
        """
        readings = [r for r in self.readings if r.location == location]
        
        if len(readings) < 2:
            return {}
        
        # Sort by time
        readings.sort(key=lambda x: x.timestamp)
        
        # Get recent readings
        latest_time = readings[-1].timestamp
        cutoff = latest_time - window_minutes * 60
        recent = [r for r in readings if r.timestamp >= cutoff]
        
        if len(recent) < 2:
            recent = readings[-2:]
        
        times = np.array([r.timestamp for r in recent])
        levels = np.array([r.level for r in recent])
        
        # Overall rate
        dt = times[-1] - times[0]
        if dt > 0:
            overall_rate = (levels[-1] - levels[0]) / dt * 3600  # m per hour
        else:
            overall_rate = 0
        
        # Instantaneous rate (gradient)
        if len(times) > 2:
            rates = np.gradient(levels, times)
            current_rate = rates[-1] * 3600
            max_rate = np.max(np.abs(rates)) * 3600
        else:
            current_rate = overall_rate
            max_rate = overall_rate
        
        return {
            'current_rate_m_per_hour': round(current_rate, 2),
            'mean_rate_m_per_hour': round(overall_rate, 2),
            'max_rate_m_per_hour': round(max_rate, 2),
            'change_m': round(levels[-1] - levels[0], 2),
            'time_window_min': window_minutes
        }

    def predict_peak_level(self,
                          current_level: float,
                          rise_rate: float,
                          time_to_peak: float) -> float:
        """
        Predict peak water level.
        
        Args:
            current_level: Current water level (m)
            rise_rate: Rise rate (m/hour)
            time_to_peak: Time to peak (hours)
            
        Returns:
            Predicted peak level (m)
        """
        return current_level + rise_rate * time_to_peak

    def calculate_flood_frequency(self,
                                 location: str,
                                 return_periods: List[int] = [2, 5, 10, 25, 50, 100]) -> Dict[int, float]:
        """
        Calculate flood frequency for different return periods.
        
        Args:
            location: Location to analyze
            return_periods: List of return periods in years
            
        Returns:
            Dictionary mapping return period to flood level
        """
        readings = [r for r in self.readings if r.location == location]
        
        if len(readings) < 10:
            return {}
        
        # Extract annual maxima (simplified - use all data)
        values = [r.level for r in readings]
        
        # Sort descending
        values.sort(reverse=True)
        
        # Calculate exceedance probabilities
        n = len(values)
        flood_levels = {}
        
        for period in return_periods:
            # Gumbel distribution approximation
            # Level = mean - std * (0.45 + 0.78 * ln(-ln(1 - 1/period)))
            mean = np.mean(values)
            std = np.std(values)
            
            if period <= n:
                # Use empirical quantile
                idx = min(int(n / period), n - 1)
                level = values[idx]
            else:
                # Extrapolate using Gumbel
                level = mean - std * (0.45 + 0.78 * np.log(-np.log(1 - 1/period)))
            
            flood_levels[period] = round(level, 2)
        
        return flood_levels

    def assess_flood_risk(self,
                         current_level: float,
                         location: str,
                         critical_level: Optional[float] = None) -> Dict[str, Union[str, float]]:
        """
        Assess current flood risk.
        
        Args:
            current_level: Current water level
            location: Location
            critical_level: Critical flood level (m)
            
        Returns:
            Dictionary with flood risk assessment
        """
        # Get historical statistics
        stats = self.calculate_statistics(location)
        
        if not stats:
            return {'status': 'Insufficient data'}
        
        if critical_level is None:
            # Use 95th percentile as critical
            values = [r.level for r in self.readings if r.location == location]
            critical_level = np.percentile(values, 95) if values else stats['max']
        
        # Calculate exceedance
        if stats['max'] > 0:
            percentile = (current_level - stats['min']) / (stats['max'] - stats['min']) * 100
        else:
            percentile = 50
        
        # Determine risk level
        if current_level >= critical_level:
            risk = "CRITICAL"
            color = "⚫"
            action = "IMMEDIATE EVACUATION"
        elif current_level > stats['max'] * 0.9:
            risk = "EXTREME"
            color = "🔴"
            action = "PREPARE TO EVACUATE"
        elif current_level > stats['max'] * 0.8:
            risk = "HIGH"
            color = "🟠"
            action = "MONITOR CLOSELY"
        elif current_level > stats['max'] * 0.6:
            risk = "MODERATE"
            color = "🟡"
            action = "CAUTION"
        else:
            risk = "LOW"
            color = "🟢"
            action = "NORMAL"
        
        # Calculate return period
        flood_freq = self.calculate_flood_frequency(location)
        return_period = None
        for period, level in flood_freq.items():
            if current_level >= level:
                return_period = period
        
        # Get rise rate
        rise_rate = self.calculate_rise_rate(location)
        
        return {
            'risk_level': risk,
            'color': color,
            'action': action,
            'current_level': current_level,
            'critical_level': critical_level,
            'percentile': round(percentile, 1),
            'historical_max': stats['max'],
            'return_period_years': return_period,
            'rise_rate': rise_rate.get('current_rate_m_per_hour', 0)
        }

    def _filter_readings(self,
                        location: Optional[str] = None,
                        start_time: Optional[float] = None,
                        end_time: Optional[float] = None) -> List[WaterLevelReading]:
        """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 water level 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] = {
                    'level': latest.level,
                    'time': latest.timestamp,
                    'discharge': latest.discharge
                }
        
        # Recent flood events
        recent_floods = sorted(self.flood_events, key=lambda x: x['start_time'], reverse=True)[:5]
        
        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,
            'recent_flood_events': recent_floods,
            'datum': self.datum
        }


# Example usage
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    
    print("="*60)
    print("CAVORA - Water Level Monitoring")
    print("="*60)
    
    wlm = WaterLevelMonitor()
    
    # Add readings
    import time
    current = time.time()
    for i in range(10):
        wlm.add_reading(current + i*3600, 1.5 + np.sin(i)*0.3, 
                       "Main Passage", "W001", discharge=0.15 + i*0.02)
    
    # Calculate statistics
    stats = wlm.calculate_statistics("Main Passage")
    print(f"\nWater Level Statistics:")
    for key, value in stats.items():
        print(f"  {key}: {value}")
    
    # Calculate rise rate
    rise_rate = wlm.calculate_rise_rate("Main Passage")
    print(f"\nRise Rate:")
    print(f"  Current: {rise_rate.get('current_rate_m_per_hour', 0)} m/hour")
    print(f"  Max: {rise_rate.get('max_rate_m_per_hour', 0)} m/hour")
    
    # Assess flood risk
    risk = wlm.assess_flood_risk(current_level=1.8, location="Main Passage")
    print(f"\nFlood Risk:")
    print(f"  {risk['color']} {risk['risk_level']}")
    print(f"  Action: {risk['action']}")
    
    print("\n" + "="*60)
