"""
Sump dynamics and water table fluctuations in cave systems.
Handles water table modeling, sump formation, and siphon dynamics.

Author: Samir Baladi
"""

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

logger = logging.getLogger(__name__)

@dataclass
class SumpState:
    """Represents the state of a sump."""
    water_level: float  # Water level relative to passage floor (m)
    volume: float  # Water volume in sump (m³)
    inflow_rate: float  # Inflow rate (m³/s)
    outflow_rate: float  # Outflow rate (m³/s)
    siphon_active: bool  # Whether siphon is flowing
    timestamp: float  # Time of observation


class SumpDynamics:
    """
    Models sump dynamics and water table fluctuations in cave systems.
    """
    
    def __init__(self,
                 passage_width: float = 3.0,
                 passage_height: float = 2.5,
                 siphon_threshold: float = 2.0,
                 siphon_diameter: float = 0.3):
        """
        Initialize sump dynamics model.
        
        Args:
            passage_width: Average passage width (m)
            passage_height: Average passage height (m)
            siphon_threshold: Water level for siphon activation (m)
            siphon_diameter: Siphon tube diameter (m)
        """
        self.passage_width = passage_width
        self.passage_height = passage_height
        self.siphon_threshold = siphon_threshold
        self.siphon_diameter = siphon_diameter
        self.siphon_area = np.pi * (siphon_diameter/2)**2
        self.states = []
        logger.info(f"Initialized SumpDynamics with threshold={siphon_threshold}m")

    def add_state(self,
                 water_level: float,
                 volume: float,
                 inflow: float,
                 outflow: float,
                 siphon_active: bool,
                 timestamp: float) -> None:
        """
        Add a sump state observation.
        
        Args:
            water_level: Water level (m)
            volume: Water volume (m³)
            inflow: Inflow rate (m³/s)
            outflow: Outflow rate (m³/s)
            siphon_active: Whether siphon is flowing
            timestamp: Observation time
        """
        state = SumpState(
            water_level=water_level,
            volume=volume,
            inflow_rate=inflow,
            outflow_rate=outflow,
            siphon_active=siphon_active,
            timestamp=timestamp
        )
        
        self.states.append(state)
        logger.debug(f"Added sump state: level={water_level:.2f}m, volume={volume:.1f}m³")

    def calculate_volume(self, water_level: float) -> float:
        """
        Calculate water volume from water level.
        
        Args:
            water_level: Water level (m)
            
        Returns:
            Water volume (m³)
        """
        # Simplified rectangular passage
        # Volume = width * length * water_level
        # Assume length = 10m for unit calculation
        passage_length = 10.0
        return self.passage_width * passage_length * water_level

    def calculate_water_level(self, volume: float) -> float:
        """
        Calculate water level from volume.
        
        Args:
            volume: Water volume (m³)
            
        Returns:
            Water level (m)
        """
        passage_length = 10.0
        if self.passage_width * passage_length > 0:
            return volume / (self.passage_width * passage_length)
        return 0

    def siphon_flow_rate(self, water_level: float) -> float:
        """
        Calculate siphon flow rate when active.
        
        Q = Cd * A * sqrt(2gH)
        
        Args:
            water_level: Current water level (m)
            
        Returns:
            Siphon flow rate (m³/s)
        """
        if not self._siphon_should_flow(water_level):
            return 0
        
        # Discharge coefficient
        Cd = 0.6
        
        # Head above siphon inlet
        H = max(0, water_level - self.siphon_threshold)
        
        g = 9.81
        Q = Cd * self.siphon_area * np.sqrt(2 * g * H)
        
        return Q

    def _siphon_should_flow(self, water_level: float) -> bool:
        """Determine if siphon should be flowing."""
        return water_level >= self.siphon_threshold

    def simulate_sump_filling(self,
                             initial_level: float,
                             inflow_rate: float,
                             duration: float,
                             dt: float = 0.1) -> Dict[str, np.ndarray]:
        """
        Simulate sump filling over time.
        
        Args:
            initial_level: Initial water level (m)
            inflow_rate: Constant inflow rate (m³/s)
            duration: Simulation duration (hours)
            dt: Time step (hours)
            
        Returns:
            Dictionary with time series
        """
        n_steps = int(duration / dt)
        times = np.linspace(0, duration, n_steps)
        
        water_levels = np.zeros(n_steps)
        volumes = np.zeros(n_steps)
        siphon_flows = np.zeros(n_steps)
        
        water_levels[0] = initial_level
        volumes[0] = self.calculate_volume(initial_level)
        
        for i in range(1, n_steps):
            # Current state
            level = water_levels[i-1]
            
            # Inflow volume over time step
            inflow_volume = inflow_rate * dt * 3600  # Convert to m³
            
            # Siphon outflow
            siphon_Q = self.siphon_flow_rate(level)
            siphon_volume = siphon_Q * dt * 3600
            siphon_flows[i] = siphon_Q
            
            # Update volume
            new_volume = volumes[i-1] + inflow_volume - siphon_volume
            volumes[i] = max(0, new_volume)
            
            # Update water level
            water_levels[i] = self.calculate_water_level(volumes[i])
        
        return {
            'time': times,
            'water_level': water_levels,
            'volume': volumes,
            'siphon_flow': siphon_flows,
            'siphon_active': water_levels >= self.siphon_threshold
        }

    def predict_sump_formation(self,
                              inflow_rate: float,
                              current_level: float,
                              critical_level: Optional[float] = None) -> Dict[str, Union[float, str]]:
        """
        Predict time to sump formation.
        
        Args:
            inflow_rate: Current inflow rate (m³/s)
            current_level: Current water level (m)
            critical_level: Level at which sump forms (defaults to passage height)
            
        Returns:
            Dictionary with prediction
        """
        if critical_level is None:
            critical_level = self.passage_height
        
        if current_level >= critical_level:
            return {
                'time_to_sump': 0,
                'status': 'Sump already formed',
                'action': 'IMMEDIATE EVACUATION'
            }
        
        # Volume to fill
        current_volume = self.calculate_volume(current_level)
        critical_volume = self.calculate_volume(critical_level)
        volume_needed = critical_volume - current_volume
        
        if volume_needed <= 0:
            return {
                'time_to_sump': 0,
                'status': 'At critical level',
                'action': 'IMMEDIATE EVACUATION'
            }
        
        if inflow_rate <= 0:
            return {
                'time_to_sump': float('inf'),
                'status': 'No inflow',
                'action': 'Monitor'
            }
        
        # Account for siphon outflow
        siphon_Q = self.siphon_flow_rate(current_level)
        net_inflow = inflow_rate - siphon_Q
        
        if net_inflow <= 0:
            return {
                'time_to_sump': float('inf'),
                'status': 'Siphon balancing inflow',
                'action': 'Monitor, level stable'
            }
        
        # Time to fill (hours)
        time_hours = volume_needed / (net_inflow * 3600)
        
        if time_hours < 1:
            status = "CRITICAL"
            action = "IMMEDIATE EVACUATION"
        elif time_hours < 3:
            status = "HIGH RISK"
            action = "PREPARE TO EVACUATE"
        elif time_hours < 6:
            status = "CAUTION"
            action = "MONITOR CLOSELY"
        else:
            status = "SAFE"
            action = "Continue monitoring"
        
        return {
            'time_to_sump': round(time_hours, 1),
            'status': status,
            'action': action,
            'current_level': round(current_level, 2),
            'critical_level': critical_level,
            'rise_needed': round(critical_level - current_level, 2),
            'inflow_rate': round(inflow_rate, 3),
            'net_inflow': round(net_inflow, 3)
        }

    def analyze_siphon_cycles(self,
                             historical_levels: Optional[np.ndarray] = None,
                             historical_times: Optional[np.ndarray] = None) -> Dict[str, Union[float, str]]:
        """
        Analyze siphon cycling behavior.
        
        Args:
            historical_levels: Historical water level data
            historical_times: Corresponding times
            
        Returns:
            Dictionary with siphon cycle analysis
        """
        if historical_levels is None and self.states:
            historical_levels = np.array([s.water_level for s in self.states])
            historical_times = np.array([s.timestamp for s in self.states])
        
        if historical_levels is None or len(historical_levels) < 10:
            return {'status': 'Insufficient data for cycle analysis'}
        
        # Detect siphon activations
        above_threshold = historical_levels >= self.siphon_threshold
        
        # Find activation events
        activations = np.where(np.diff(above_threshold.astype(int)) > 0)[0]
        deactivations = np.where(np.diff(above_threshold.astype(int)) < 0)[0]
        
        if len(activations) == 0:
            return {
                'status': 'No siphon activations detected',
                'threshold': self.siphon_threshold
            }
        
        # Calculate cycle characteristics
        if len(activations) > 1 and len(deactivations) > 0:
            cycle_starts = historical_times[activations[:-1]]
            cycle_ends = historical_times[deactivations[:len(activations)-1]]
            cycle_durations = cycle_ends - cycle_starts
            
            mean_duration = np.mean(cycle_durations)
            std_duration = np.std(cycle_durations)
            
            # Period between cycles
            if len(cycle_starts) > 1:
                periods = np.diff(cycle_starts)
                mean_period = np.mean(periods)
            else:
                mean_period = float('inf')
        else:
            mean_duration = float('inf')
            std_duration = 0
            mean_period = float('inf')
        
        return {
            'n_cycles': len(activations),
            'threshold': self.siphon_threshold,
            'mean_duration_hours': round(mean_duration * 24, 1) if mean_duration != float('inf') else 'N/A',
            'duration_std': round(std_duration * 24, 1),
            'mean_period_days': round(mean_period, 1) if mean_period != float('inf') else 'N/A',
            'siphon_efficiency': round(len(deactivations) / len(activations) * 100, 1) if activations.size > 0 else 0
        }

    def get_sump_safety_status(self,
                              water_level: float,
                              rate_of_rise: float) -> Dict[str, Union[str, float]]:
        """
        Get safety status for sump conditions.
        
        Args:
            water_level: Current water level (m)
            rate_of_rise: Rate of water level rise (m/hr)
            
        Returns:
            Dictionary with safety status
        """
        # Calculate freeboard
        freeboard = self.passage_height - water_level
        
        if freeboard <= 0:
            status = "SUBMERGED"
            color = "⚫"
            action = "IMMEDIATE EVACUATION"
        elif freeboard < 0.5:
            status = "CRITICAL"
            color = "🔴"
            action = "IMMEDIATE EVACUATION"
        elif freeboard < 1.0:
            status = "HIGH RISK"
            color = "🟠"
            action = "PREPARE TO EVACUATE"
        elif freeboard < 1.5:
            status = "CAUTION"
            color = "🟡"
            action = "MONITOR CLOSELY"
        else:
            status = "SAFE"
            color = "🟢"
            action = "Normal operations"
        
        # Time to critical level
        if rate_of_rise > 0:
            time_to_critical = (0.5 - freeboard) / rate_of_rise if freeboard < 0.5 else float('inf')
            time_to_submerge = freeboard / rate_of_rise
        else:
            time_to_critical = float('inf')
            time_to_submerge = float('inf')
        
        return {
            'status': status,
            'color': color,
            'action': action,
            'freeboard': round(freeboard, 2),
            'water_level': round(water_level, 2),
            'passage_height': self.passage_height,
            'rate_of_rise': round(rate_of_rise, 3),
            'time_to_critical_hours': round(time_to_critical, 1) if time_to_critical != float('inf') else '>24h',
            'time_to_submerge_hours': round(time_to_submerge, 1) if time_to_submerge != float('inf') else '>24h'
        }

    def summary(self) -> Dict[str, Union[float, str]]:
        """Get summary of sump dynamics."""
        if not self.states:
            return {'status': 'No observations'}
        
        levels = [s.water_level for s in self.states]
        
        return {
            'n_observations': len(self.states),
            'mean_level': round(np.mean(levels), 2),
            'max_level': round(max(levels), 2),
            'min_level': round(min(levels), 2),
            'siphon_threshold': self.siphon_threshold,
            'passage_height': self.passage_height,
            'siphon_diameter': self.siphon_diameter,
            'times_siphon_active': sum(1 for s in self.states if s.siphon_active)
        }


# Example usage
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    
    print("="*60)
    print("CAVORA - Sump Dynamics Analysis")
    print("="*60)
    
    sd = SumpDynamics(passage_height=2.5, siphon_threshold=2.0)
    
    # Simulate sump filling
    sim = sd.simulate_sump_filling(initial_level=1.5, inflow_rate=0.1, duration=24)
    print(f"\nSump Filling Simulation:")
    print(f"  - Final Level: {sim['water_level'][-1]:.2f}m")
    print(f"  - Max Level: {max(sim['water_level']):.2f}m")
    print(f"  - Siphon Active: {any(sim['siphon_active'])}")
    
    # Predict sump formation
    prediction = sd.predict_sump_formation(inflow_rate=0.15, current_level=1.8)
    print(f"\nSump Formation Prediction:")
    print(f"  - Time to Sump: {prediction['time_to_sump']} hours")
    print(f"  - Status: {prediction['status']}")
    print(f"  - Action: {prediction['action']}")
    
    # Safety status
    safety = sd.get_sump_safety_status(water_level=2.2, rate_of_rise=0.1)
    print(f"\nSump Safety Status:")
    print(f"  - Status: {safety['color']} {safety['status']}")
    print(f"  - Action: {safety['action']}")
    print(f"  - Freeboard: {safety['freeboard']}m")
    
    print("\n" + "="*60)
