"""
CDZ Detection - Identifies Creative Dissolution Zones in cave passages.
Uses multiple criteria to detect areas of rapid morphological change.

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 CDZEvent:
    """Represents a detected Creative Dissolution Zone."""
    location: str
    start_time: float
    end_time: float
    peak_divergence: float  # Maximum morphological divergence (m)
    transition_type: str  # Type of phase transition
    confidence: float  # Detection confidence (0-1)


class CDZDetector:
    """
    Detects Creative Dissolution Zones using multiple criteria from the paper:
    1. Trajectory divergence > 1.5σ within single season
    2. Rapid phase transition > 0.5 m/season
    3. High CO₂ flux > 500 ppm/hour
    """
    
    def __init__(self,
                 divergence_threshold: float = 1.5,  # σ units
                 transition_threshold: float = 0.5,  # m/season
                 co2_threshold: float = 500):  # ppm/hour
        """
        Initialize CDZ detector.
        
        Args:
            divergence_threshold: Trajectory divergence threshold (σ)
            transition_threshold: Rapid phase transition threshold (m/season)
            co2_threshold: CO₂ flux threshold (ppm/hour)
        """
        self.divergence_threshold = divergence_threshold
        self.transition_threshold = transition_threshold
        self.co2_threshold = co2_threshold
        self.cdz_events = []
        logger.info(f"Initialized CDZDetector with thresholds: divergence={divergence_threshold}σ, "
                   f"transition={transition_threshold}m/season, CO₂={co2_threshold}ppm/hr")

    def detect_from_trajectory(self,
                              time: np.ndarray,
                              V: np.ndarray,
                              L: Optional[np.ndarray] = None,
                              F: Optional[np.ndarray] = None,
                              CO2: Optional[np.ndarray] = None,
                              location: str = "Unknown") -> List[Dict[str, Union[str, float]]]:
        """
        Detect CDZs from trajectory data.
        
        Args:
            time: Time array (years)
            V: Vertical passage time series
            L: Optional lateral conduit time series
            F: Optional flowstone time series
            CO2: Optional CO₂ concentration time series
            location: Location identifier
            
        Returns:
            List of detected CDZ events
        """
        logger.info(f"Detecting CDZs in {location}")
        
        # Calculate criteria
        divergence_cdz = self._detect_divergence(time, V)
        transition_cdz = self._detect_transition(time, V, L)
        co2_cdz = self._detect_co2_flux(time, CO2) if CO2 is not None else []
        
        # Combine detections
        all_cdz = self._combine_detections(divergence_cdz, transition_cdz, co2_cdz, time)
        
        # Create CDZ events
        events = []
        for cdz in all_cdz:
            event = {
                'location': location,
                'start_time': float(cdz['start']),
                'end_time': float(cdz['end']),
                'duration': float(cdz['end'] - cdz['start']),
                'peak_divergence': float(cdz.get('peak_divergence', 0)),
                'transition_type': self._classify_transition(cdz),
                'confidence': float(cdz['confidence']),
                'criteria_met': cdz['criteria']
            }
            events.append(event)
            self.cdz_events.append(CDZEvent(**event))
        
        logger.info(f"Detected {len(events)} CDZ events")
        return events

    def _detect_divergence(self, time: np.ndarray, V: np.ndarray) -> List[Dict]:
        """
        Detect CDZs based on trajectory divergence.
        """
        if len(V) < 5:
            return []
        
        # Calculate moving statistics
        window = max(3, len(V) // 10)
        
        cdz_regions = []
        i = 0
        while i < len(V) - window:
            # Local mean and std
            local_mean = np.mean(V[i:i+window])
            local_std = np.std(V[i:i+window])
            
            if local_std == 0:
                i += 1
                continue
            
            # Check for divergence
            for j in range(i, min(i + window, len(V))):
                z_score = abs(V[j] - local_mean) / local_std
                
                if z_score > self.divergence_threshold:
                    # Found divergence, expand region
                    start = max(0, j - window//2)
                    end = min(len(V), j + window//2)
                    
                    # Calculate peak
                    region = V[start:end]
                    peak_idx = start + np.argmax(np.abs(region - np.mean(region)))
                    
                    cdz_regions.append({
                        'start': time[start],
                        'end': time[end-1],
                        'peak_time': time[peak_idx],
                        'peak_divergence': float(abs(V[peak_idx] - np.mean(region)) / local_std),
                        'criteria': ['divergence']
                    })
                    
                    i = end
                    break
            else:
                i += 1
        
        return cdz_regions

    def _detect_transition(self, time: np.ndarray, V: np.ndarray, L: Optional[np.ndarray]) -> List[Dict]:
        """
        Detect CDZs based on rapid phase transitions.
        """
        if len(time) < 4:
            return []
        
        # Calculate rate of change
        dt = np.mean(np.diff(time))
        dVdt = np.gradient(V, time)
        
        if L is not None:
            dLdt = np.gradient(L, time)
            rate = np.sqrt(dVdt**2 + dLdt**2)
        else:
            rate = np.abs(dVdt)
        
        # Convert to m/season (assuming time in years)
        rate_per_season = rate * 0.25  # Convert annual to seasonal
        
        # Find regions above threshold
        above_threshold = rate_per_season > self.transition_threshold
        
        cdz_regions = []
        i = 0
        while i < len(above_threshold):
            if above_threshold[i]:
                start = i
                while i < len(above_threshold) and above_threshold[i]:
                    i += 1
                end = i
                
                cdz_regions.append({
                    'start': time[start],
                    'end': time[end-1],
                    'peak_rate': float(np.max(rate_per_season[start:end])),
                    'criteria': ['transition']
                })
            else:
                i += 1
        
        return cdz_regions

    def _detect_co2_flux(self, time: np.ndarray, CO2: np.ndarray) -> List[Dict]:
        """
        Detect CDZs based on high CO₂ flux.
        """
        if CO2 is None or len(CO2) < 4:
            return []
        
        # Calculate CO₂ flux (rate of change)
        dt_hours = np.mean(np.diff(time)) * 365.25 * 24  # Convert years to hours
        dCO2dt = np.gradient(CO2, time) / dt_hours if dt_hours > 0 else np.zeros_like(CO2)
        
        # Find regions above threshold
        above_threshold = np.abs(dCO2dt) > self.co2_threshold
        
        cdz_regions = []
        i = 0
        while i < len(above_threshold):
            if above_threshold[i]:
                start = i
                while i < len(above_threshold) and above_threshold[i]:
                    i += 1
                end = i
                
                cdz_regions.append({
                    'start': time[start],
                    'end': time[end-1],
                    'peak_flux': float(np.max(np.abs(dCO2dt[start:end]))),
                    'criteria': ['co2_flux']
                })
            else:
                i += 1
        
        return cdz_regions

    def _combine_detections(self, *detections: List[List[Dict]], time: np.ndarray) -> List[Dict]:
        """
        Combine multiple detection criteria.
        """
        # Flatten and sort all detections
        all_regions = []
        for det_list in detections:
            all_regions.extend(det_list)
        
        if not all_regions:
            return []
        
        # Sort by start time
        all_regions.sort(key=lambda x: x['start'])
        
        # Merge overlapping regions
        merged = []
        current = all_regions[0].copy()
        
        for region in all_regions[1:]:
            if region['start'] <= current['end']:
                # Overlapping, merge
                current['end'] = max(current['end'], region['end'])
                current['criteria'] = list(set(current['criteria'] + region.get('criteria', [])))
                if 'peak_divergence' in region:
                    current['peak_divergence'] = max(current.get('peak_divergence', 0), 
                                                     region.get('peak_divergence', 0))
            else:
                # Calculate confidence based on number of criteria
                current['confidence'] = len(current['criteria']) / 3
                merged.append(current)
                current = region.copy()
        
        current['confidence'] = len(current['criteria']) / 3
        merged.append(current)
        
        return merged

    def _classify_transition(self, cdz: Dict) -> str:
        """
        Classify the type of transition.
        """
        # Simplified classification based on timing
        # In practice, would use more sophisticated analysis
        
        if 'peak_divergence' in cdz and cdz['peak_divergence'] > 2.5:
            return "Major reorganization"
        elif len(cdz.get('criteria', [])) >= 2:
            return "Active dissolution"
        else:
            return "Minor adjustment"

    def calculate_cdz_proximity(self,
                               current_state: Tuple[float, float, float],
                               historical_cdz: List[Dict]) -> float:
        """
        Calculate proximity to known CDZs.
        
        Args:
            current_state: Current (V, L, F) state
            historical_cdz: List of historical CDZ events
            
        Returns:
            CDZ proximity score (0-1)
        """
        if not historical_cdz:
            return 0.0
        
        V, L, F = current_state
        
        # Calculate distance to each CDZ in phase space
        distances = []
        for cdz in historical_cdz:
            # Simplified distance calculation
            # In practice, would use full phase space distance
            dist = abs(V - cdz.get('peak_divergence', 2.5)) / 5.0
            distances.append(min(1.0, dist))
        
        # Proximity is inverse of minimum distance
        min_dist = min(distances) if distances else 1.0
        proximity = 1.0 - min_dist
        
        return round(proximity, 2)

    def get_cdz_statistics(self) -> Dict[str, Union[float, str]]:
        """Get statistics of detected CDZs."""
        if not self.cdz_events:
            return {'status': 'No CDZs detected'}
        
        durations = [e.duration for e in self.cdz_events]
        divergences = [e.peak_divergence for e in self.cdz_events]
        
        return {
            'n_cdz': len(self.cdz_events),
            'mean_duration': round(np.mean(durations), 2),
            'max_duration': round(max(durations), 2),
            'mean_divergence': round(np.mean(divergences), 2),
            'max_divergence': round(max(divergences), 2),
            'locations': list(set(e.location for e in self.cdz_events))
        }


# Example usage
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    
    print("="*60)
    print("CAVORA - CDZ Detection")
    print("="*60)
    
    # Generate sample data
    t = np.linspace(0, 10, 1000)
    # Normal oscillation with a CDZ at t=5
    V = 2.5 + 0.2 * np.sin(2 * np.pi * 0.5 * t)
    # Add CDZ
    cdz_region = (t > 4.5) & (t < 5.5)
    V[cdz_region] += 1.5 * np.exp(-((t[cdz_region] - 5)**2) / 0.1)
    
    detector = CDZDetector()
    
    # Detect CDZs
    cdzs = detector.detect_from_trajectory(t, V, location="Test Passage")
    
    print(f"\nDetected CDZs: {len(cdzs)}")
    for i, cdz in enumerate(cdzs):
        print(f"\nCDZ {i+1}:")
        print(f"  - Period: {cdz['start_time']:.2f} - {cdz['end_time']:.2f} years")
        print(f"  - Duration: {cdz['duration']:.2f} years")
        print(f"  - Peak Divergence: {cdz['peak_divergence']:.2f}σ")
        print(f"  - Type: {cdz['transition_type']}")
        print(f"  - Confidence: {cdz['confidence']:.0%}")
    
    print("\n" + "="*60)
