"""
CDZ Classification - Categorizes Creative Dissolution Zones by type and characteristics.
Implements the classification system from the research paper.

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 CDZClass:
    """Represents a classified CDZ type."""
    class_name: str
    subclass: str
    characteristics: List[str]
    hazard_level: str  # Low, Moderate, High, Extreme
    typical_duration: float  # months
    recurrence_interval: float  # years


class CDZClassifier:
    """
    Classifies Creative Dissolution Zones based on their characteristics.
    
    Classification from paper:
    - By transition type: Phreatic→Vadose, Vadose→Phreatic, Conduit→Breakdown, Stable→Sump
    - By spatial distribution: Vertical-dominated, Lateral-dominated, Mixed geometry
    """
    
    # Classification schema from paper
    CDZ_TYPES = {
        'phreatic_vadose': {
            'name': 'Phreatic-Vadose Transition',
            'description': 'Transition from water-filled to air-filled conditions',
            'typical_duration': 3.2,  # months
            'hazard_level': 'High',
            'color': '🔴',
            'indicators': ['Water table fluctuation', 'Rapid dissolution', 'Morphology change']
        },
        'vadose_phreatic': {
            'name': 'Vadose-Phreatic Transition',
            'description': 'Transition from air-filled to water-filled conditions',
            'typical_duration': 2.9,  # months
            'hazard_level': 'Extreme',
            'color': '⚫',
            'indicators': ['Rising water level', 'Sump formation', 'Backflooding']
        },
        'conduit_breakdown': {
            'name': 'Conduit-Breakdown Transition',
            'description': 'Passage collapse and chamber formation',
            'typical_duration': 4.7,  # months
            'hazard_level': 'Extreme',
            'color': '⚫',
            'indicators': ['Roof instability', 'Fracture propagation', 'Rockfall']
        },
        'stable_sump': {
            'name': 'Stable-Sump Transition',
            'description': 'Development of permanent water bodies',
            'typical_duration': 2.3,  # months
            'hazard_level': 'High',
            'color': '🔴',
            'indicators': ['Water level rise', 'Reduced flow', 'Siphon formation']
        }
    }
    
    SPATIAL_TYPES = {
        'vertical_dominated': {
            'name': 'Vertical-Dominated',
            'description': 'CDZ controlled by vertical passage dynamics',
            'v_contribution': 0.7,
            'l_contribution': 0.2,
            'f_contribution': 0.1
        },
        'lateral_dominated': {
            'name': 'Lateral-Dominated',
            'description': 'CDZ controlled by lateral conduit dynamics',
            'v_contribution': 0.2,
            'l_contribution': 0.7,
            'f_contribution': 0.1
        },
        'mixed_geometry': {
            'name': 'Mixed Geometry',
            'description': 'CDZ with balanced V-L-F contributions',
            'v_contribution': 0.4,
            'l_contribution': 0.4,
            'f_contribution': 0.2
        }
    }
    
    def __init__(self):
        """Initialize CDZ classifier."""
        self.classifications = []
        logger.info("Initialized CDZClassifier")

    def classify_by_transition(self,
                              v_history: np.ndarray,
                              l_history: np.ndarray,
                              f_history: np.ndarray,
                              time: np.ndarray) -> Dict[str, Union[str, float, List]]:
        """
        Classify CDZ by transition type based on VLF trajectories.
        
        Args:
            v_history: Vertical passage history
            l_history: Lateral conduit history
            f_history: Flowstone history
            time: Time array
            
        Returns:
            Dictionary with classification
        """
        if len(v_history) < 10:
            return {'status': 'Insufficient data'}
        
        # Calculate trends
        v_trend = np.polyfit(time, v_history, 1)[0]
        l_trend = np.polyfit(time, l_history, 1)[0]
        f_trend = np.polyfit(time, f_history, 1)[0]
        
        # Detect transition type based on relative changes
        if v_trend > 0.1 and l_trend < -0.05:
            # V increasing, L decreasing - likely phreatic to vadose
            transition_type = 'phreatic_vadose'
            confidence = min(1.0, abs(v_trend) / 0.2)
            
        elif v_trend < -0.05 and l_trend > 0.1:
            # V decreasing, L increasing - likely vadose to phreatic
            transition_type = 'vadose_phreatic'
            confidence = min(1.0, abs(l_trend) / 0.2)
            
        elif v_trend < -0.2 and l_trend > 0.2:
            # Rapid V decrease, L increase - likely conduit to breakdown
            transition_type = 'conduit_breakdown'
            confidence = min(1.0, (abs(v_trend) + abs(l_trend)) / 0.5)
            
        elif f_trend > 0.05 and v_trend < -0.05:
            # F increasing, V decreasing - likely stable to sump
            transition_type = 'stable_sump'
            confidence = min(1.0, f_trend / 0.1)
            
        else:
            transition_type = 'unknown'
            confidence = 0.3
        
        if transition_type in self.CDZ_TYPES:
            cdz_type = self.CDZ_TYPES[transition_type]
            result = {
                'transition_type': transition_type,
                'class_name': cdz_type['name'],
                'description': cdz_type['description'],
                'hazard_level': cdz_type['hazard_level'],
                'color': cdz_type['color'],
                'confidence': round(confidence, 2),
                'typical_duration_months': cdz_type['typical_duration'],
                'indicators': cdz_type['indicators'],
                'v_trend': round(v_trend, 3),
                'l_trend': round(l_trend, 3),
                'f_trend': round(f_trend, 3)
            }
        else:
            result = {
                'transition_type': 'unknown',
                'class_name': 'Unclassified Transition',
                'confidence': round(confidence, 2),
                'v_trend': round(v_trend, 3),
                'l_trend': round(l_trend, 3),
                'f_trend': round(f_trend, 3)
            }
        
        self.classifications.append(result)
        return result

    def classify_spatial(self,
                        v_change: float,
                        l_change: float,
                        f_change: float) -> Dict[str, Union[str, float]]:
        """
        Classify CDZ by spatial dominance.
        
        Args:
            v_change: Change in V dimension
            l_change: Change in L dimension
            f_change: Change in F dimension
            
        Returns:
            Dictionary with spatial classification
        """
        # Normalize changes
        total = abs(v_change) + abs(l_change) + abs(f_change)
        if total == 0:
            return {'spatial_type': 'No change', 'confidence': 0}
        
        v_contrib = abs(v_change) / total
        l_contrib = abs(l_change) / total
        f_contrib = abs(f_change) / total
        
        # Determine dominant type
        if v_contrib > 0.6:
            spatial_type = 'vertical_dominated'
            confidence = v_contrib
        elif l_contrib > 0.6:
            spatial_type = 'lateral_dominated'
            confidence = l_contrib
        elif v_contrib > 0.3 and l_contrib > 0.3:
            spatial_type = 'mixed_geometry'
            confidence = v_contrib + l_contrib
        else:
            spatial_type = 'f_dominated'
            confidence = f_contrib
        
        if spatial_type in self.SPATIAL_TYPES:
            st = self.SPATIAL_TYPES[spatial_type]
            result = {
                'spatial_type': spatial_type,
                'class_name': st['name'],
                'description': st['description'],
                'confidence': round(confidence, 2),
                'v_contribution': round(v_contrib, 2),
                'l_contribution': round(l_contrib, 2),
                'f_contribution': round(f_contrib, 2)
            }
        else:
            result = {
                'spatial_type': spatial_type,
                'confidence': round(confidence, 2),
                'v_contribution': round(v_contrib, 2),
                'l_contribution': round(l_contrib, 2),
                'f_contribution': round(f_contrib, 2)
            }
        
        return result

    def classify_by_intensity(self,
                             peak_divergence: float,
                             duration: float,
                             co2_flux: Optional[float] = None) -> Dict[str, Union[str, float]]:
        """
        Classify CDZ by intensity.
        
        Args:
            peak_divergence: Peak morphological divergence (σ)
            duration: CDZ duration (months)
            co2_flux: Optional CO₂ flux (ppm/hr)
            
        Returns:
            Dictionary with intensity classification
        """
        # Intensity score (0-1)
        intensity_score = min(1.0, peak_divergence / 3.0)
        
        if peak_divergence > 2.5:
            intensity = "EXTREME"
            color = "⚫"
            description = "Major reorganization, immediate danger"
        elif peak_divergence > 2.0:
            intensity = "HIGH"
            color = "🔴"
            description = "Active dissolution, high hazard"
        elif peak_divergence > 1.5:
            intensity = "MODERATE"
            color = "🟠"
            description = "Significant activity, monitor closely"
        else:
            intensity = "LOW"
            color = "🟡"
            description = "Minor adjustment, caution advised"
        
        # Adjust for CO₂ if available
        if co2_flux and co2_flux > 1000:
            intensity = "EXTREME"
            color = "⚫"
            description += " - Extreme CO₂ flux"
            intensity_score = min(1.0, intensity_score * 1.5)
        
        return {
            'intensity_level': intensity,
            'color': color,
            'description': description,
            'intensity_score': round(intensity_score, 2),
            'peak_divergence': peak_divergence,
            'duration_months': duration
        }

    def classify_by_location(self,
                            depth: float,
                            proximity_to_water: float,
                            fracture_density: float) -> Dict[str, Union[str, float]]:
        """
        Classify CDZ by location characteristics.
        
        Args:
            depth: Depth below surface (m)
            proximity_to_water: Distance to water table (m)
            fracture_density: Local fracture density (fractures/m)
            
        Returns:
            Dictionary with location classification
        """
        # Depth classification
        if depth < 50:
            depth_zone = "Shallow"
            depth_risk = "Moderate"
        elif depth < 200:
            depth_zone = "Intermediate"
            depth_risk = "High"
        else:
            depth_zone = "Deep"
            depth_risk = "Extreme"
        
        # Water proximity classification
        if proximity_to_water < 5:
            water_zone = "Water table interface"
            water_risk = "Extreme"
        elif proximity_to_water < 20:
            water_zone = "Near water table"
            water_risk = "High"
        else:
            water_zone = "Above water table"
            water_risk = "Moderate"
        
        # Fracture density classification
        if fracture_density > 5:
            fracture_zone = "Highly fractured"
            fracture_risk = "Extreme"
        elif fracture_density > 2:
            fracture_zone = "Moderately fractured"
            fracture_risk = "High"
        else:
            fracture_zone = "Massive"
            fracture_risk = "Moderate"
        
        # Overall risk (max of individual risks)
        risk_levels = {'Low': 1, 'Moderate': 2, 'High': 3, 'Extreme': 4}
        overall_risk = max(risk_levels[depth_risk], 
                          risk_levels[water_risk], 
                          risk_levels[fracture_risk])
        overall_risk_name = [k for k, v in risk_levels.items() if v == overall_risk][0]
        
        return {
            'depth_zone': depth_zone,
            'water_zone': water_zone,
            'fracture_zone': fracture_zone,
            'overall_risk': overall_risk_name,
            'depth': depth,
            'proximity_to_water': proximity_to_water,
            'fracture_density': fracture_density
        }

    def get_complete_classification(self,
                                   v_history: np.ndarray,
                                   l_history: np.ndarray,
                                   f_history: np.ndarray,
                                   time: np.ndarray,
                                   metadata: Dict) -> Dict[str, Union[str, float, Dict]]:
        """
        Get complete CDZ classification using all criteria.
        
        Args:
            v_history: Vertical passage history
            l_history: Lateral conduit history
            f_history: Flowstone history
            time: Time array
            metadata: Additional metadata (depth, location, etc.)
            
        Returns:
            Complete classification dictionary
        """
        # Get transition classification
        transition = self.classify_by_transition(v_history, l_history, f_history, time)
        
        # Calculate changes for spatial classification
        v_change = v_history[-1] - v_history[0] if len(v_history) > 1 else 0
        l_change = l_history[-1] - l_history[0] if len(l_history) > 1 else 0
        f_change = f_history[-1] - f_history[0] if len(f_history) > 1 else 0
        
        # Get spatial classification
        spatial = self.classify_spatial(v_change, l_change, f_change)
        
        # Get intensity classification
        peak_divergence = metadata.get('peak_divergence', 2.0)
        duration = metadata.get('duration_months', 3.0)
        co2_flux = metadata.get('co2_flux')
        intensity = self.classify_by_intensity(peak_divergence, duration, co2_flux)
        
        # Get location classification
        depth = metadata.get('depth', 100)
        water_distance = metadata.get('water_distance', 10)
        fracture_density = metadata.get('fracture_density', 2)
        location = self.classify_by_location(depth, water_distance, fracture_density)
        
        # Overall classification
        classification = {
            'transition': transition,
            'spatial': spatial,
            'intensity': intensity,
            'location': location,
            'overall_hazard': self._calculate_overall_hazard(transition, intensity, location),
            'recommendations': self._get_recommendations(transition, intensity, location)
        }
        
        self.classifications.append(classification)
        return classification

    def _calculate_overall_hazard(self,
                                  transition: Dict,
                                  intensity: Dict,
                                  location: Dict) -> Dict[str, Union[str, float]]:
        """Calculate overall hazard level."""
        hazard_weights = {'Low': 1, 'Moderate': 2, 'High': 3, 'Extreme': 4}
        
        # Get hazard levels
        trans_hazard = transition.get('hazard_level', 'Moderate')
        inten_hazard = intensity.get('intensity_level', 'Moderate')
        loc_hazard = location.get('overall_risk', 'Moderate')
        
        # Calculate weighted score
        weighted_score = (hazard_weights.get(trans_hazard, 2) * 0.4 +
                         hazard_weights.get(inten_hazard, 2) * 0.4 +
                         hazard_weights.get(loc_hazard, 2) * 0.2)
        
        # Convert back to level
        if weighted_score >= 3.5:
            overall = 'EXTREME'
            color = '⚫'
        elif weighted_score >= 2.5:
            overall = 'HIGH'
            color = '🔴'
        elif weighted_score >= 1.5:
            overall = 'MODERATE'
            color = '🟠'
        else:
            overall = 'LOW'
            color = '🟡'
        
        return {
            'level': overall,
            'color': color,
            'score': round(weighted_score, 2)
        }

    def _get_recommendations(self,
                            transition: Dict,
                            intensity: Dict,
                            location: Dict) -> List[str]:
        """Get safety recommendations based on classification."""
        recommendations = []
        
        # Transition-based recommendations
        trans_type = transition.get('transition_type', '')
        if trans_type == 'conduit_breakdown':
            recommendations.append("IMMEDIATE EVACUATION - Active breakdown zone")
            recommendations.append("Avoid areas with fresh rockfall")
        elif trans_type == 'vadose_phreatic':
            recommendations.append("Monitor water level continuously")
            recommendations.append("Prepare for sump formation")
        elif trans_type == 'phreatic_vadose':
            recommendations.append("Watch for unstable ceiling")
            recommendations.append("Document morphological changes")
        
        # Intensity-based recommendations
        inten_level = intensity.get('intensity_level', '')
        if inten_level == 'EXTREME':
            recommendations.append("Maximum alert - exit immediately")
        elif inten_level == 'HIGH':
            recommendations.append("Enhanced monitoring required")
            recommendations.append("Limit exposure time")
        
        # Location-based recommendations
        loc_risk = location.get('overall_risk', '')
        if loc_risk == 'Extreme':
            recommendations.append("High-risk zone - avoid if possible")
        elif loc_risk == 'High':
            recommendations.append("Exercise extreme caution")
        
        return list(set(recommendations))  # Remove duplicates

    def summary(self) -> Dict[str, Union[int, List, str]]:
        """Get summary of classifications."""
        if not self.classifications:
            return {'status': 'No classifications'}
        
        # Count by type
        type_counts = {}
        for c in self.classifications:
            if isinstance(c, dict) and 'transition' in c:
                trans = c['transition'].get('transition_type', 'unknown')
                type_counts[trans] = type_counts.get(trans, 0) + 1
        
        return {
            'n_classifications': len(self.classifications),
            'type_distribution': type_counts,
            'last_classification': self.classifications[-1] if self.classifications else None
        }


# Example usage
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    
    print("="*60)
    print("CAVORA - CDZ Classification")
    print("="*60)
    
    classifier = CDZClassifier()
    
    # Generate sample data
    t = np.linspace(0, 5, 100)
    # Simulate a phreatic-vadose transition
    V = 2.0 + 0.8 * (1 - np.exp(-t/2))
    L = 3.0 - 0.4 * t
    F = 0.08 + 0.01 * t
    
    # Classify by transition
    transition = classifier.classify_by_transition(V, L, F, t)
    print(f"\nTransition Classification:")
    print(f"  - Type: {transition.get('class_name', 'Unknown')}")
    print(f"  - Hazard: {transition.get('color', '')} {transition.get('hazard_level', 'Unknown')}")
    print(f"  - Confidence: {transition.get('confidence', 0):.0%}")
    
    # Classify spatially
    spatial = classifier.classify_spatial(V[-1]-V[0], L[-1]-L[0], F[-1]-F[0])
    print(f"\nSpatial Classification:")
    print(f"  - Type: {spatial.get('class_name', 'Unknown')}")
    print(f"  - V Contribution: {spatial.get('v_contribution', 0):.0%}")
    
    # Complete classification
    metadata = {
        'peak_divergence': 2.3,
        'duration_months': 3.5,
        'co2_flux': 650,
        'depth': 85,
        'water_distance': 3,
        'fracture_density': 2.8
    }
    
    complete = classifier.get_complete_classification(V, L, F, t, metadata)
    print(f"\nComplete Classification:")
    print(f"  - Overall Hazard: {complete['overall_hazard']['color']} {complete['overall_hazard']['level']}")
    print(f"\nRecommendations:")
    for rec in complete['recommendations']:
        print(f"  • {rec}")
    
    print("\n" + "="*60)
