"""
Pressure Sensing Module - Ambient pressure monitoring
"""

import numpy as np
from typing import List, Tuple, Dict, Optional
from dataclasses import dataclass
from enum import Enum


class PressureSource(Enum):
    """Sources of pressure fluctuations"""
    TURBIDITY_CURRENT = "turbidity_current"
    SEISMIC_MICROSEISM = "seismic_microseism"
    BIOLOGICAL = "biological"
    CURRENT_TURBULENCE = "current_turbulence"
    UNKNOWN = "unknown"


@dataclass
class PressureReading:
    """Single pressure measurement"""
    pressure: float  # MPa
    depth: float  # meters
    temperature: float  # °C
    timestamp: float  # seconds


@dataclass
class PressureFluctuation:
    """Pressure fluctuation event"""
    amplitude: float  # Pa
    frequency: float  # Hz
    duration: float  # seconds
    source: PressureSource
    energy: float  # J/m²


class PressureSensing:
    """
    Ambient pressure monitoring and fluctuation analysis
    
    Detects pressure variations from turbidity currents, seismic activity,
    and biological sources for energy harvesting potential
    """
    
    def __init__(self,
                 pressure_range: Tuple[float, float] = (0, 120),  # MPa
                 sample_rate: float = 10,  # Hz
                 sensitivity: float = 1.0):  # Pa
        """
        Initialize pressure sensor
        
        Args:
            pressure_range: Pressure measurement range (MPa)
            sample_rate: Sampling rate in Hz
            sensitivity: Pressure sensitivity in Pa
        """
        self.pressure_range = pressure_range
        self.sample_rate = sample_rate
        self.sensitivity = sensitivity
        
        # Physical constants
        self.gravity = 9.81  # m/s²
        self.water_density = 1050  # kg/m³ (at depth)
        
        # History
        self.readings = []
        self.fluctuations = []
        self.spectra = []
    
    def measure(self,
               depth: float,
               duration: float = 3600) -> List[PressureReading]:
        """
        Measure pressure over time period
        
        Args:
            depth: Measurement depth in meters
            duration: Measurement duration in seconds
            
        Returns:
            List of pressure readings
        """
        num_samples = int(duration * self.sample_rate)
        time = np.linspace(0, duration, num_samples)
        
        readings = []
        
        # Base hydrostatic pressure
        P_base = depth * 0.01007  # MPa
        
        for t in time:
            # Add fluctuations from various sources
            pressure = P_base
            
            # Turbidity current fluctuations (0.05 Hz, ±2 kPa)
            pressure += 0.002 * np.sin(2 * np.pi * 0.05 * t) * np.random.normal(1, 0.2)
            
            # Seismic microseisms (0.1-0.3 Hz, ±87 Pa)
            pressure += 0.000087 * np.sin(2 * np.pi * 0.2 * t) * np.random.normal(1, 0.3)
            
            # Current turbulence (1-2 Hz, ±23 Pa)
            pressure += 0.000023 * np.sin(2 * np.pi * 1.5 * t) * np.random.normal(1, 0.4)
            
            # Biological activity (4-12 Hz, ±8 Pa)
            pressure += 0.000008 * np.sin(2 * np.pi * 8 * t) * np.random.normal(1, 0.5)
            
            # Add measurement noise
            pressure += np.random.normal(0, self.sensitivity / 1e6)  # Convert Pa to MPa
            
            # Ensure within range
            pressure = np.clip(pressure, self.pressure_range[0], self.pressure_range[1])
            
            reading = PressureReading(
                pressure=pressure,
                depth=depth,
                temperature=2.0,  # Constant for now
                timestamp=t
            )
            
            readings.append(reading)
            self.readings.append(reading)
        
        return readings
    
    def detect_oscillations(self,
                           readings: List[PressureReading],
                           window: float = 60.0) -> List[PressureFluctuation]:
        """
        Detect pressure oscillations in time series
        
        Args:
            readings: List of pressure readings
            window: Analysis window in seconds
            
        Returns:
            List of detected fluctuations
        """
        if len(readings) < 2:
            return []
        
        # Convert to numpy arrays
        time = np.array([r.timestamp for r in readings])
        pressure = np.array([r.pressure for r in readings])
        
        # Detrend
        pressure_detrend = pressure - np.mean(pressure)
        
        # Find zero crossings for oscillation detection
        zero_crossings = np.where(np.diff(np.sign(pressure_detrend)))[0]
        
        fluctuations = []
        
        for i in range(len(zero_crossings) - 1):
            start = zero_crossings[i]
            end = zero_crossings[i + 1]
            
            if end - start < 5:  # Minimum samples for oscillation
                continue
            
            # Extract segment
            t_seg = time[start:end]
            p_seg = pressure_detrend[start:end]
            
            # Calculate characteristics
            duration = t_seg[-1] - t_seg[0]
            amplitude = np.max(np.abs(p_seg)) * 1e6  # Convert to Pa
            frequency = 1.0 / duration if duration > 0 else 0
            
            # Classify source
            source = self.classify_source(amplitude, frequency)
            
            # Calculate energy (simplified)
            energy = 0.5 * amplitude**2 * duration / self.water_density  # J/m²
            
            fluctuation = PressureFluctuation(
                amplitude=amplitude,
                frequency=frequency,
                duration=duration,
                source=source,
                energy=energy
            )
            
            fluctuations.append(fluctuation)
            self.fluctuations.append(fluctuation)
        
        return fluctuations
    
    def classify_source(self,
                       amplitude: float,
                       frequency: float) -> PressureSource:
        """
        Classify source of pressure fluctuation
        
        Args:
            amplitude: Fluctuation amplitude in Pa
            frequency: Fluctuation frequency in Hz
            
        Returns:
            PressureSource classification
        """
        # Turbidity currents: ±0.5-2 kPa, 0.03-0.1 Hz
        if (500 <= amplitude <= 2000 and
            0.03 <= frequency <= 0.1):
            return PressureSource.TURBIDITY_CURRENT
        
        # Seismic microseisms: ±50-200 Pa, 0.1-0.3 Hz
        elif (50 <= amplitude <= 200 and
              0.1 <= frequency <= 0.3):
            return PressureSource.SEISMIC_MICROSEISM
        
        # Current turbulence: ±10-50 Pa, 0.8-2.5 Hz
        elif (10 <= amplitude <= 50 and
              0.8 <= frequency <= 2.5):
            return PressureSource.CURRENT_TURBULENCE
        
        # Biological: ±2-15 Pa, 4-12 Hz
        elif (2 <= amplitude <= 15 and
              4 <= frequency <= 12):
            return PressureSource.BIOLOGICAL
        
        return PressureSource.UNKNOWN
    
    def spectral_analysis(self,
                         readings: List[PressureReading]) -> Dict:
        """
        Perform spectral analysis on pressure readings
        
        Args:
            readings: List of pressure readings
            
        Returns:
            Spectral analysis results
        """
        if len(readings) < 100:
            return {}
        
        # Extract pressure values
        pressure = np.array([r.pressure for r in readings])
        
        # Detrend
        pressure = pressure - np.mean(pressure)
        
        # Compute FFT
        n = len(pressure)
        dt = 1.0 / self.sample_rate
        freqs = np.fft.rfftfreq(n, dt)
        fft = np.fft.rfft(pressure)
        power = np.abs(fft)**2 / n
        
        # Find peaks in different frequency bands
        def find_peak_in_band(f_min, f_max):
            mask = (freqs >= f_min) & (freqs <= f_max)
            if not np.any(mask):
                return 0, 0
            band_power = power[mask]
            band_freqs = freqs[mask]
            peak_idx = np.argmax(band_power)
            return band_freqs[peak_idx], band_power[peak_idx]
        
        turb_freq, turb_power = find_peak_in_band(0.03, 0.1)
        seis_freq, seis_power = find_peak_in_band(0.1, 0.3)
        curr_freq, curr_power = find_peak_in_band(0.8, 2.5)
        bio_freq, bio_power = find_peak_in_band(4, 12)
        
        return {
            'turbidity_current': {
                'frequency': float(turb_freq),
                'power': float(turb_power)
            },
            'seismic_microseism': {
                'frequency': float(seis_freq),
                'power': float(seis_power)
            },
            'current_turbulence': {
                'frequency': float(curr_freq),
                'power': float(curr_power)
            },
            'biological': {
                'frequency': float(bio_freq),
                'power': float(bio_power)
            }
        }
    
    def pressure_at_depth(self, depth: float) -> float:
        """Calculate hydrostatic pressure at depth"""
        return depth * 0.01007  # MPa
    
    def depth_from_pressure(self, pressure: float) -> float:
        """Calculate depth from pressure"""
        return pressure / 0.01007  # meters
    
    def get_statistics(self) -> Dict:
        """Get pressure sensing statistics"""
        if not self.readings:
            return {
                'num_readings': 0,
                'num_fluctuations': 0,
                'avg_pressure': 0,
                'pressure_std': 0
            }
        
        pressures = [r.pressure for r in self.readings]
        
        # Count fluctuations by source
        source_counts = {}
        for f in self.fluctuations:
            source_counts[f.source.value] = source_counts.get(f.source.value, 0) + 1
        
        # Average energy by source
        source_energy = {}
        for f in self.fluctuations:
            source_energy[f.source.value] = source_energy.get(f.source.value, 0) + f.energy
        
        return {
            'num_readings': len(self.readings),
            'num_fluctuations': len(self.fluctuations),
            'avg_pressure': float(np.mean(pressures)),
            'pressure_std': float(np.std(pressures)),
            'max_pressure': float(np.max(pressures)),
            'min_pressure': float(np.min(pressures)),
            'source_distribution': source_counts,
            'source_energy': source_energy
        }
    
    def __repr__(self) -> str:
        stats = self.get_statistics()
        return (f"PressureSensing(readings={stats['num_readings']}, "
                f"fluctuations={stats['num_fluctuations']}, "
                f"P_avg={stats['avg_pressure']:.2f}MPa)")
