"""
Fault Analytics

Tracks fault metrics and calculates reliability statistics.
"""

import time
from typing import Dict, List, Optional
from dataclasses import dataclass
import threading


@dataclass
class FaultEvent:
    """Record of a fault event"""
    module_id: str
    fault_type: str
    timestamp: float
    severity: str
    recovered: bool = False
    recovery_time: Optional[float] = None


class FaultAnalytics:
    """
    Tracks and analyzes fault patterns and recovery metrics.

    Metrics:
    - MTBF (Mean Time Between Failures)
    - MTTR (Mean Time To Recovery)
    - Fault frequency by module
    - Fault correlation analysis
    """

    def __init__(self):
        self._fault_events: List[FaultEvent] = []
        self._recovery_events: Dict[str, float] = {}  # fault_id -> recovery_time
        self._lock = threading.Lock()

        # Metrics cache
        self._metrics_cache: Optional[Dict] = None
        self._cache_timestamp: float = 0
        self._cache_ttl: float = 5.0  # Cache for 5 seconds

    def record_fault(
        self,
        module_id: str,
        fault_type: str,
        severity: str,
        fault_id: Optional[str] = None
    ) -> str:
        """
        Record a fault event.

        Returns:
            fault_id for tracking recovery
        """
        with self._lock:
            if fault_id is None:
                fault_id = f"{module_id}_{len(self._fault_events)}"

            event = FaultEvent(
                module_id=module_id,
                fault_type=fault_type,
                timestamp=time.time(),
                severity=severity
            )

            self._fault_events.append(event)

            # Keep only last 10000 events
            if len(self._fault_events) > 10000:
                self._fault_events = self._fault_events[-10000:]

            # Invalidate cache
            self._metrics_cache = None

            return fault_id

    def record_recovery(self, module_id: str, recovery_time: float):
        """Record successful recovery from a fault"""
        with self._lock:
            # Find most recent unrecovered fault for this module
            for event in reversed(self._fault_events):
                if event.module_id == module_id and not event.recovered:
                    event.recovered = True
                    event.recovery_time = recovery_time
                    break

            # Invalidate cache
            self._metrics_cache = None

    def calculate_mtbf(self, module_id: Optional[str] = None) -> float:
        """
        Calculate Mean Time Between Failures.

        Args:
            module_id: Calculate for specific module, or all if None

        Returns:
            MTBF in seconds
        """
        with self._lock:
            events = self._fault_events
            if module_id:
                events = [e for e in events if e.module_id == module_id]

            if len(events) < 2:
                return 0.0

            # Calculate time between consecutive faults
            intervals = []
            for i in range(1, len(events)):
                interval = events[i].timestamp - events[i-1].timestamp
                intervals.append(interval)

            return sum(intervals) / len(intervals) if intervals else 0.0

    def calculate_mttr(self, module_id: Optional[str] = None) -> float:
        """
        Calculate Mean Time To Recovery.

        Args:
            module_id: Calculate for specific module, or all if None

        Returns:
            MTTR in seconds
        """
        with self._lock:
            events = self._fault_events
            if module_id:
                events = [e for e in events if e.module_id == module_id]

            recovery_times = [
                e.recovery_time for e in events
                if e.recovered and e.recovery_time is not None
            ]

            if not recovery_times:
                return 0.0

            return sum(recovery_times) / len(recovery_times)

    def get_fault_frequency(
        self,
        time_window: float = 3600.0
    ) -> Dict[str, int]:
        """
        Get fault frequency by module within time window.

        Args:
            time_window: Time window in seconds (default 1 hour)

        Returns:
            Dict mapping module_id to fault count
        """
        with self._lock:
            cutoff = time.time() - time_window
            recent_faults = [
                e for e in self._fault_events
                if e.timestamp >= cutoff
            ]

            frequency = {}
            for event in recent_faults:
                frequency[event.module_id] = frequency.get(event.module_id, 0) + 1

            return frequency

    def get_fault_correlation(self) -> List[Dict]:
        """
        Analyze fault correlations (which faults tend to occur together).

        Returns:
            List of correlated fault patterns
        """
        with self._lock:
            # Simple correlation: faults within 10 seconds
            correlation_window = 10.0
            correlations = []

            for i in range(len(self._fault_events) - 1):
                current = self._fault_events[i]
                next_fault = self._fault_events[i + 1]

                time_diff = next_fault.timestamp - current.timestamp
                if time_diff <= correlation_window:
                    correlations.append({
                        "first_module": current.module_id,
                        "first_type": current.fault_type,
                        "second_module": next_fault.module_id,
                        "second_type": next_fault.fault_type,
                        "time_diff": time_diff
                    })

            return correlations[-100:]  # Return last 100 correlations

    def get_metrics(self) -> Dict:
        """Get comprehensive fault analytics metrics"""
        # Check cache
        if (self._metrics_cache and
            time.time() - self._cache_timestamp < self._cache_ttl):
            return self._metrics_cache

        with self._lock:
            total_faults = len(self._fault_events)
            recovered_faults = sum(1 for e in self._fault_events if e.recovered)

            # Fault counts by module
            module_counts = {}
            severity_counts = {}

            for event in self._fault_events:
                module_counts[event.module_id] = module_counts.get(event.module_id, 0) + 1
                severity_counts[event.severity] = severity_counts.get(event.severity, 0) + 1

            metrics = {
                "total_faults": total_faults,
                "recovered_faults": recovered_faults,
                "recovery_rate": recovered_faults / total_faults if total_faults > 0 else 0.0,
                "mtbf_seconds": self.calculate_mtbf(),
                "mttr_seconds": self.calculate_mttr(),
                "faults_by_module": module_counts,
                "faults_by_severity": severity_counts,
                "recent_frequency": self.get_fault_frequency(time_window=3600.0)
            }

            # Cache results
            self._metrics_cache = metrics
            self._cache_timestamp = time.time()

            return metrics

    def get_module_health_score(self, module_id: str) -> float:
        """
        Calculate health score for a module (0.0 to 1.0).

        Higher score = healthier module
        """
        with self._lock:
            module_events = [e for e in self._fault_events if e.module_id == module_id]

            if not module_events:
                return 1.0  # No faults = perfect health

            # Recent faults (last hour) weigh more
            recent_cutoff = time.time() - 3600.0
            recent_faults = sum(1 for e in module_events if e.timestamp >= recent_cutoff)

            # Recovery rate
            recovered = sum(1 for e in module_events if e.recovered)
            recovery_rate = recovered / len(module_events)

            # Calculate score (simple heuristic)
            # Penalize recent faults more heavily
            score = 1.0
            score -= min(recent_faults * 0.1, 0.5)  # Max 50% penalty for recent faults
            score *= recovery_rate  # Multiply by recovery rate

            return max(0.0, min(1.0, score))
