"""
Vertical Passage Control (V) - Structural Integrity parameter.
Handles measurement, analysis, and prediction of vertical passage dimensions.

Equation: V(t) = α₀ + Σ αᵢ sin(ωᵢt + φᵢ)

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 VerticalMeasurement:
    """Represents a single vertical passage measurement."""
    value: float  # Measured height (m)
    timestamp: float  # Time of measurement (years)
    inclination: float  # Inclination angle (degrees)
    precision: float  # Measurement precision (m)
    station_id: str  # Survey station identifier
    notes: Optional[str] = None


class VerticalPassage:
    """
    Handles vertical passage measurements and analysis.
    
    Attributes:
        alpha0: Baseline passage height (m)
        alphas: Amplitudes of oscillations
        omegas: Frequencies of oscillations (rad/yr)
        phis: Phase offsets (radians)
    """
    
    def __init__(self, alpha0: float = 2.5):
        """
        Initialize vertical passage analyzer.
        
        Args:
            alpha0: Baseline passage height (default 2.5m from paper)
        """
        self.alpha0 = alpha0
        self.alphas = []
        self.omegas = []
        self.phis = []
        self.measurements = []
        logger.info(f"Initialized VerticalPassage with baseline α₀={alpha0}m")

    def add_measurement(self, 
                       height: float, 
                       inclination: float,
                       timestamp: float,
                       station_id: str,
                       precision: float = 0.05) -> None:
        """
        Add a new vertical passage measurement.
        
        Args:
            height: Measured height (m)
            inclination: Inclination angle (degrees)
            timestamp: Time of measurement (years)
            station_id: Survey station identifier
            precision: Measurement precision (default 0.05m from paper)
        """
        # Correct for inclination
        corrected_height = height * np.cos(np.radians(inclination))
        
        measurement = VerticalMeasurement(
            value=corrected_height,
            timestamp=timestamp,
            inclination=inclination,
            precision=precision,
            station_id=station_id
        )
        
        self.measurements.append(measurement)
        logger.info(f"Added vertical measurement at {station_id}: {corrected_height:.2f}m")

    def calculate_time_series(self, 
                            t: np.ndarray,
                            n_components: int = 3) -> np.ndarray:
        """
        Calculate vertical passage time series from Fourier components.
        
        V(t) = α₀ + Σ αᵢ sin(ωᵢt + φᵢ)
        
        Args:
            t: Time array (years)
            n_components: Number of Fourier components to use
            
        Returns:
            Vertical passage time series
        """
        V = np.ones_like(t) * self.alpha0
        
        for i in range(min(n_components, len(self.alphas))):
            V += self.alphas[i] * np.sin(self.omegas[i] * t + self.phis[i])
        
        return V

    def fit_to_measurements(self, 
                           n_components: int = 3) -> Dict[str, np.ndarray]:
        """
        Fit Fourier series to measurement data.
        
        Args:
            n_components: Number of Fourier components to fit
            
        Returns:
            Dictionary with fitted parameters
        """
        if len(self.measurements) < 2:
            logger.warning("Insufficient measurements for fitting")
            return {}
        
        # Extract timestamps and values
        t = np.array([m.timestamp for m in self.measurements])
        V = np.array([m.value for m in self.measurements])
        
        # Sort by time
        idx = np.argsort(t)
        t = t[idx]
        V = V[idx]
        
        # Simple Fourier fit using FFT
        dt = np.mean(np.diff(t))
        if dt <= 0:
            dt = 1.0
        
        # Detrend
        V_detrend = V - np.mean(V)
        
        # FFT
        fft = np.fft.fft(V_detrend)
        freqs = np.fft.fftfreq(len(V_detrend), dt)
        
        # Find dominant frequencies
        magnitudes = np.abs(fft)
        
        # Take top n_components positive frequencies
        positive_idx = freqs > 0
        top_idx = np.argsort(magnitudes[positive_idx])[-n_components:]
        
        self.alphas = []
        self.omegas = []
        self.phis = []
        
        for idx in top_idx:
            actual_idx = np.where(positive_idx)[0][idx]
            self.alphas.append(2 * magnitudes[actual_idx] / len(V))
            self.omegas.append(2 * np.pi * freqs[actual_idx])
            self.phis.append(np.angle(fft[actual_idx]))
        
        # Baseline is mean
        self.alpha0 = float(np.mean(V))
        
        logger.info(f"Fitted {n_components} Fourier components to {len(self.measurements)} measurements")
        
        return {
            'alpha0': self.alpha0,
            'alphas': self.alphas,
            'omegas': self.omegas,
            'phis': self.phis
        }

    def calculate_structural_integrity(self, 
                                      current_height: Optional[float] = None) -> Dict[str, Union[float, str]]:
        """
        Calculate structural integrity score based on vertical passage.
        
        Args:
            current_height: Current measured height (uses latest if None)
            
        Returns:
            Dictionary with integrity score and assessment
        """
        if current_height is None and self.measurements:
            current_height = self.measurements[-1].value
        elif current_height is None:
            return {'score': 0, 'assessment': 'No measurements available'}
        
        # Target height from paper
        target_height = 2.5  # m
        
        # Calculate deviation
        deviation = abs(current_height - target_height) / target_height
        
        # Score (100 - scaled deviation)
        score = max(0, 100 * (1 - min(1, deviation)))
        
        # Assessment
        if score >= 80:
            assessment = "High structural integrity"
        elif score >= 60:
            assessment = "Moderate structural integrity"
        elif score >= 40:
            assessment = "Low structural integrity"
        elif score >= 20:
            assessment = "Poor structural integrity"
        else:
            assessment = "Critical structural failure risk"
        
        return {
            'score': round(score, 1),
            'assessment': assessment,
            'current_height': current_height,
            'target_height': target_height,
            'deviation': round(deviation * 100, 1)
        }

    def predict_ceiling_stability(self, 
                                 span: float,
                                 rock_density: float = 2700) -> Dict[str, Union[float, str]]:
        """
        Predict ceiling stability based on roof span analysis.
        
        Args:
            span: Roof span width (m)
            rock_density: Rock density (kg/m³, default 2700 for limestone)
            
        Returns:
            Dictionary with stability prediction
        """
        # Simplified beam theory for roof stability
        # Maximum safe span for given height
        
        # Empirical relationship from paper
        safe_span = 2.5 * self.alpha0  # Rough approximation
        
        safety_factor = safe_span / span if span > 0 else float('inf')
        
        if safety_factor >= 2.0:
            status = "Stable"
            risk = "Low"
        elif safety_factor >= 1.5:
            status = "Caution"
            risk = "Moderate"
        elif safety_factor >= 1.0:
            status = "Unstable"
            risk = "High"
        else:
            status = "Critical"
            risk = "Imminent collapse risk"
        
        return {
            'span': span,
            'safe_span': safe_span,
            'safety_factor': round(safety_factor, 2),
            'status': status,
            'risk': risk,
            'recommendation': self._get_span_recommendation(safety_factor)
        }

    def _get_span_recommendation(self, safety_factor: float) -> str:
        """Get recommendation based on safety factor."""
        if safety_factor >= 2.0:
            return "Safe for traversal"
        elif safety_factor >= 1.5:
            return "Exercise caution, monitor for cracks"
        elif safety_factor >= 1.0:
            return "Avoid area, notify safety officer"
        else:
            return "IMMEDIATE EVACUATION - Collapse risk"

    def get_regulatory_compliance(self, 
                                 max_allowed_height: float = 3.0) -> Dict[str, Union[bool, str]]:
        """
        Check compliance with regulatory height limits.
        
        Args:
            max_allowed_height: Maximum allowed height (m)
            
        Returns:
            Dictionary with compliance status
        """
        if not self.measurements:
            return {'compliant': False, 'message': 'No measurements available'}
        
        latest = self.measurements[-1].value
        compliant = latest <= max_allowed_height
        
        return {
            'compliant': compliant,
            'current_height': latest,
            'max_allowed': max_allowed_height,
            'message': 'Within limits' if compliant else f'Exceeds limit by {latest - max_allowed_height:.2f}m'
        }

    def summary(self) -> Dict[str, Union[float, int, str]]:
        """Get summary statistics of vertical passage."""
        if not self.measurements:
            return {'status': 'No data'}
        
        values = [m.value for m in self.measurements]
        
        return {
            'n_measurements': len(self.measurements),
            'mean_height': np.mean(values),
            'std_height': np.std(values),
            'min_height': np.min(values),
            'max_height': np.max(values),
            'latest_height': values[-1],
            'latest_station': self.measurements[-1].station_id,
            'baseline': self.alpha0
        }


# Example usage
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    
    print("="*60)
    print("CAVORA - Vertical Passage Analysis")
    print("="*60)
    
    vp = VerticalPassage()
    
    # Add some measurements
    vp.add_measurement(2.8, 5.0, 2021.5, "V-001")
    vp.add_measurement(2.9, 6.0, 2022.5, "V-002")
    vp.add_measurement(3.1, 4.0, 2023.5, "V-003")
    
    # Fit Fourier components
    fit = vp.fit_to_measurements()
    print(f"\nFitted parameters:")
    print(f"  - α₀ = {fit.get('alpha0', 0):.2f} m")
    
    # Calculate structural integrity
    integrity = vp.calculate_structural_integrity()
    print(f"\nStructural Integrity:")
    print(f"  - Score: {integrity['score']}/100")
    print(f"  - Assessment: {integrity['assessment']}")
    
    # Predict ceiling stability
    stability = vp.predict_ceiling_stability(span=3.5)
    print(f"\nCeiling Stability:")
    print(f"  - Status: {stability['status']}")
    print(f"  - Safety Factor: {stability['safety_factor']}")
    print(f"  - Recommendation: {stability['recommendation']}")
    
    # Summary
    summary = vp.summary()
    print(f"\nSummary:")
    print(f"  - Measurements: {summary['n_measurements']}")
    print(f"  - Mean Height: {summary['mean_height']:.2f} m")
    
    print("\n" + "="*60)
