"""
Circuit Breaker for CAAE eviction decisions.

Prevents PCIe bus saturation by switching from LWKCP to LRU mode
when queue depth exceeds threshold. Validated in Experiment 5.
"""

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

from .config import CAAEConfig, get_config


class CircuitState(Enum):
    """Circuit breaker states."""

    CLOSED = "closed"  # Normal operation, LWKCP active
    OPEN = "open"  # Tripped, falling back to LRU
    HALF_OPEN = "half_open"  # Testing if conditions improved


@dataclass
class CircuitBreakerStats:
    """Statistics for circuit breaker monitoring."""

    total_checks: int = 0
    trips: int = 0
    current_state: CircuitState = CircuitState.CLOSED
    last_trip_time: Optional[float] = None
    last_recovery_time: Optional[float] = None
    consecutive_successes: int = 0
    consecutive_failures: int = 0


class CircuitBreaker:
    """
    Circuit breaker to prevent PCIe queue saturation.

    When PCIe queue depth exceeds threshold, the circuit "trips" and
    forces conservative (LRU/recompute) behavior until conditions improve.

    States:
    - CLOSED: Normal operation, cost-aware decisions (LWKCP)
    - OPEN: Queue saturated, force recompute to prevent further saturation
    - HALF_OPEN: Testing recovery with limited swap operations

    Threshold calibration (from Experiment 5):
    - Gen 4: 5.0ms threshold (170 triggers at 40 RPS baseline)
    - Gen 5: 5.0ms threshold (~0 triggers under normal load)
    """

    # Recovery configuration
    RECOVERY_THRESHOLD = 3  # Consecutive successes needed to close circuit
    FAILURE_THRESHOLD = 2  # Consecutive failures to open circuit

    def __init__(
        self,
        threshold_ms: float | None = None,
        cooldown_ms: float | None = None,
        config: CAAEConfig | None = None,
    ):
        """
        Initialize circuit breaker.

        Args:
            threshold_ms: Queue depth threshold to trip circuit
            cooldown_ms: Time before attempting recovery
            config: CAAE configuration
        """
        self.config = config or get_config()
        self.threshold_ms = threshold_ms or self.config.circuit_breaker_threshold_ms
        self.cooldown_ms = cooldown_ms or self.config.circuit_breaker_cooldown_ms

        # Validate threshold
        if self.threshold_ms <= 0:
            raise ValueError(
                f"Circuit breaker threshold must be > 0, got {self.threshold_ms}"
            )

        self._state = CircuitState.CLOSED
        self._last_trip_time: float = 0.0
        self._consecutive_successes = 0
        self._consecutive_failures = 0

        # Time-based failure tracking for rapid transitions
        self._failure_window_seconds = 5.0
        self._recent_failures: List[float] = []

        # Metrics
        self._stats = CircuitBreakerStats()

    @property
    def state(self) -> CircuitState:
        """Get current circuit state."""
        return self._state

    @property
    def is_active(self) -> bool:
        """Check if circuit breaker is currently active (tripped)."""
        return self._state != CircuitState.CLOSED

    def check(self, queue_depth_ms: float) -> bool:
        """
        Check if circuit should trip based on current queue depth.

        Args:
            queue_depth_ms: Current PCIe queue depth in milliseconds

        Returns:
            True if circuit is active (should use conservative policy),
            False if normal operation allowed
        """
        self._stats.total_checks += 1
        current_time = time.time()

        if self._state == CircuitState.CLOSED:
            # Normal operation - check if we should trip
            if queue_depth_ms > self.threshold_ms:
                # Track failure in time window
                self._recent_failures.append(current_time)
                # Remove failures outside window
                self._recent_failures = [
                    t
                    for t in self._recent_failures
                    if current_time - t < self._failure_window_seconds
                ]

                self._consecutive_failures += 1
                self._consecutive_successes = 0

                # Trip if threshold exceeded OR enough failures in time window
                if (
                    self._consecutive_failures >= self.FAILURE_THRESHOLD
                    or len(self._recent_failures) >= self.FAILURE_THRESHOLD
                ):
                    self._trip(current_time)
                    return True
            else:
                self._consecutive_failures = 0
                self._consecutive_successes += 1
                # Clear recent failures on success
                self._recent_failures = []
            return False

        elif self._state == CircuitState.OPEN:
            # Circuit is tripped - check if cooldown elapsed
            elapsed_ms = (current_time - self._last_trip_time) * 1000

            if elapsed_ms >= self.cooldown_ms:
                # Try half-open state
                self._state = CircuitState.HALF_OPEN
                self._consecutive_successes = 0
                return self._check_half_open(queue_depth_ms, current_time)

            return True  # Still in cooldown

        else:  # HALF_OPEN
            return self._check_half_open(queue_depth_ms, current_time)

    def _check_half_open(self, queue_depth_ms: float, current_time: float) -> bool:
        """Handle half-open state logic."""
        if queue_depth_ms <= self.threshold_ms:
            self._consecutive_successes += 1
            self._consecutive_failures = 0

            if self._consecutive_successes >= self.RECOVERY_THRESHOLD:
                self._recover(current_time)
                return False

            # Still testing, allow operation
            return False
        else:
            # Failed during recovery - trip again
            self._consecutive_failures += 1
            self._consecutive_successes = 0
            self._trip(current_time)
            return True

    def _trip(self, current_time: float) -> None:
        """Trip the circuit breaker."""
        self._state = CircuitState.OPEN
        self._last_trip_time = current_time
        self._stats.trips += 1
        self._stats.current_state = CircuitState.OPEN
        self._stats.last_trip_time = current_time

    def _recover(self, current_time: float) -> None:
        """Recover circuit breaker to closed state."""
        self._state = CircuitState.CLOSED
        self._consecutive_failures = 0
        self._stats.current_state = CircuitState.CLOSED
        self._stats.last_recovery_time = current_time

    def force_open(self) -> None:
        """Force circuit breaker open (for testing/emergency)."""
        self._trip(time.time())

    def force_close(self) -> None:
        """Force circuit breaker closed (for testing/recovery)."""
        self._recover(time.time())

    def get_stats(self) -> CircuitBreakerStats:
        """Get circuit breaker statistics."""
        self._stats.current_state = self._state
        self._stats.consecutive_successes = self._consecutive_successes
        self._stats.consecutive_failures = self._consecutive_failures
        return self._stats

    def reset_stats(self) -> None:
        """Reset statistics (but not state)."""
        self._stats = CircuitBreakerStats(current_state=self._state)
