"""
Circuit breaker for provider failure management.

Tracks consecutive failures per provider and "opens" the circuit (stops calls)
when failures exceed a threshold. Automatically resets after a timeout period.
"""

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


@dataclass
class CircuitState:
    """State for a single provider's circuit breaker."""

    consecutive_failures: int = 0
    last_failure_time: float = 0.0
    is_open: bool = False
    open_time: float = 0.0

    # Stats for reporting
    total_successes: int = 0
    total_failures: int = 0


class CircuitBreaker:
    """Per-provider circuit breaker with automatic reset.

    When a provider experiences too many consecutive failures, the circuit
    "opens" and subsequent calls return immediately without attempting the
    provider. After a reset timeout, the circuit "half-opens" and allows
    one test call. If that succeeds, the circuit closes; if it fails, it
    reopens.

    Attributes:
        failure_threshold: Number of consecutive failures to open circuit
        reset_timeout_seconds: Time before circuit transitions to half-open
    """

    def __init__(
        self,
        failure_threshold: int = 5,
        reset_timeout_seconds: float = 60.0,
    ):
        """Initialize circuit breaker.

        Args:
            failure_threshold: Consecutive failures before circuit opens
            reset_timeout_seconds: Seconds before circuit can reset
        """
        self.failure_threshold = failure_threshold
        self.reset_timeout_seconds = reset_timeout_seconds
        self._states: Dict[str, CircuitState] = {}

    def _get_state(self, provider: str) -> CircuitState:
        """Get or create state for a provider."""
        if provider not in self._states:
            self._states[provider] = CircuitState()
        return self._states[provider]

    def is_open(self, provider: str) -> bool:
        """Check if circuit is open for a provider.

        Returns True if the provider should be skipped due to too many failures.
        Handles the half-open transition automatically.

        Args:
            provider: Provider name

        Returns:
            True if circuit is open and provider should be skipped
        """
        state = self._get_state(provider)

        if not state.is_open:
            return False

        # Check if enough time has passed to try again (half-open)
        elapsed = time.monotonic() - state.open_time
        if elapsed >= self.reset_timeout_seconds:
            # Transition to half-open: allow one test call
            # We don't actually close yet - record_success will do that
            return False

        return True

    def record_success(self, provider: str) -> None:
        """Record a successful call for a provider.

        Resets consecutive failure count and closes the circuit if open.

        Args:
            provider: Provider name
        """
        state = self._get_state(provider)
        state.consecutive_failures = 0
        state.is_open = False
        state.total_successes += 1

    def record_failure(self, provider: str) -> None:
        """Record a failed call for a provider.

        Increments failure count and opens circuit if threshold exceeded.
        If already in half-open state, immediately reopens the circuit.

        Args:
            provider: Provider name
        """
        state = self._get_state(provider)
        now = time.monotonic()

        state.consecutive_failures += 1
        state.last_failure_time = now
        state.total_failures += 1

        # If circuit was open (in half-open state allowing test), reopen immediately
        if state.is_open:
            state.open_time = now  # Reset the open time
        elif state.consecutive_failures >= self.failure_threshold:
            state.is_open = True
            state.open_time = now

    def get_status(self, provider: str) -> Dict[str, any]:
        """Get circuit breaker status for a provider.

        Args:
            provider: Provider name

        Returns:
            Dictionary with circuit state information
        """
        state = self._get_state(provider)

        status = "closed"
        if state.is_open:
            elapsed = time.monotonic() - state.open_time
            if elapsed >= self.reset_timeout_seconds:
                status = "half-open"
            else:
                status = "open"

        return {
            "provider": provider,
            "status": status,
            "consecutive_failures": state.consecutive_failures,
            "total_successes": state.total_successes,
            "total_failures": state.total_failures,
            "is_open": state.is_open,
        }

    def get_all_status(self) -> Dict[str, Dict[str, any]]:
        """Get circuit breaker status for all providers.

        Returns:
            Dictionary mapping provider names to status dicts
        """
        return {provider: self.get_status(provider) for provider in self._states}

    def reset(self, provider: Optional[str] = None) -> None:
        """Reset circuit breaker state.

        Args:
            provider: If specified, reset only this provider; otherwise reset all
        """
        if provider:
            if provider in self._states:
                self._states[provider] = CircuitState()
        else:
            self._states.clear()
