"""
Fault Classifier

Classifies faults by severity and determines recovery strategies.
"""

from enum import Enum
from typing import Dict, Optional, List
from dataclasses import dataclass
import time


class FaultSeverity(Enum):
    """Fault severity levels"""
    SOFT = "SOFT"          # Temporary, self-recovering
    HARD = "HARD"          # Requires intervention
    CRITICAL = "CRITICAL"  # System-threatening


@dataclass
class FaultPattern:
    """Pattern for fault classification"""
    name: str
    severity: FaultSeverity
    module_patterns: List[str]  # Glob patterns
    fault_type_patterns: List[str]
    frequency_threshold: Optional[int] = None  # Faults per time window
    time_window: float = 60.0  # seconds

    def matches(self, module_id: str, fault_type: str) -> bool:
        """Check if this pattern matches the fault"""
        module_match = any(
            self._matches_pattern(module_id, pattern)
            for pattern in self.module_patterns
        )

        type_match = any(
            self._matches_pattern(fault_type, pattern)
            for pattern in self.fault_type_patterns
        )

        return module_match and type_match

    def _matches_pattern(self, value: str, pattern: str) -> bool:
        """Simple glob pattern matching"""
        if pattern == "*":
            return True
        if pattern.endswith("*"):
            prefix = pattern[:-1]
            return value.startswith(prefix)
        return value == pattern


class FaultClassifier:
    """
    Classifies faults by severity and selects recovery strategies.

    Features:
    - Pattern-based classification
    - Frequency-based escalation
    - Fault pattern recognition
    - Severity-based recovery selection
    """

    def __init__(self):
        self._patterns: List[FaultPattern] = []
        self._fault_history: List[Dict] = []

        # Default patterns
        self._add_default_patterns()

    def _add_default_patterns(self):
        """Add default classification patterns"""
        # Heartbeat timeouts are typically SOFT (temporary)
        self._patterns.append(
            FaultPattern(
                name="heartbeat_timeout",
                severity=FaultSeverity.SOFT,
                module_patterns=["*"],
                fault_type_patterns=["HEARTBEAT_TIMEOUT", "heartbeat*"],
                frequency_threshold=3,
                time_window=60.0
            )
        )

        # Crashes are HARD
        self._patterns.append(
            FaultPattern(
                name="module_crash",
                severity=FaultSeverity.HARD,
                module_patterns=["*"],
                fault_type_patterns=["CRASH", "crash*", "FATAL*"],
            )
        )

        # Critical modules failing is CRITICAL
        self._patterns.append(
            FaultPattern(
                name="critical_module_failure",
                severity=FaultSeverity.CRITICAL,
                module_patterns=["kernel*", "core*", "safety*"],
                fault_type_patterns=["*"],
            )
        )

    def add_pattern(self, pattern: FaultPattern):
        """Add a custom classification pattern"""
        self._patterns.append(pattern)

    def classify(
        self,
        module_id: str,
        fault_type: str,
        metadata: Optional[Dict] = None
    ) -> FaultSeverity:
        """
        Classify a fault by severity.

        Args:
            module_id: ID of the faulting module
            fault_type: Type of fault
            metadata: Additional context

        Returns:
            FaultSeverity level
        """
        # Record fault
        self._fault_history.append({
            "timestamp": time.time(),
            "module_id": module_id,
            "fault_type": fault_type,
            "metadata": metadata or {}
        })

        # Keep only recent history (last 1000 faults)
        if len(self._fault_history) > 1000:
            self._fault_history = self._fault_history[-1000:]

        # Check patterns in order (first match wins)
        for pattern in self._patterns:
            if pattern.matches(module_id, fault_type):
                # Check frequency threshold if specified
                if pattern.frequency_threshold:
                    recent_count = self._count_recent_faults(
                        module_id,
                        fault_type,
                        pattern.time_window
                    )
                    if recent_count >= pattern.frequency_threshold:
                        # Escalate to next severity level
                        return self._escalate_severity(pattern.severity)

                return pattern.severity

        # Default to SOFT if no pattern matches
        return FaultSeverity.SOFT

    def _count_recent_faults(
        self,
        module_id: str,
        fault_type: str,
        time_window: float
    ) -> int:
        """Count faults matching criteria within time window"""
        cutoff = time.time() - time_window
        count = 0

        for fault in reversed(self._fault_history):
            if fault["timestamp"] < cutoff:
                break
            if (fault["module_id"] == module_id and
                fault["fault_type"] == fault_type):
                count += 1

        return count

    def _escalate_severity(self, severity: FaultSeverity) -> FaultSeverity:
        """Escalate severity to next level"""
        if severity == FaultSeverity.SOFT:
            return FaultSeverity.HARD
        elif severity == FaultSeverity.HARD:
            return FaultSeverity.CRITICAL
        return FaultSeverity.CRITICAL

    def get_fault_statistics(self) -> Dict:
        """Get fault statistics"""
        severity_counts = {
            FaultSeverity.SOFT: 0,
            FaultSeverity.HARD: 0,
            FaultSeverity.CRITICAL: 0
        }

        module_counts = {}

        for fault in self._fault_history:
            # Reclassify to get severity
            severity = self.classify(
                fault["module_id"],
                fault["fault_type"],
                fault["metadata"]
            )
            severity_counts[severity] += 1

            module_id = fault["module_id"]
            module_counts[module_id] = module_counts.get(module_id, 0) + 1

        return {
            "total_faults": len(self._fault_history),
            "by_severity": {k.value: v for k, v in severity_counts.items()},
            "by_module": module_counts
        }
