"""Circuit breaker pattern implementation."""
from __future__ import annotations

import asyncio
import enum
import time
import logging

logger = logging.getLogger("runblob.circuit_breaker")


class CircuitState(enum.Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"


class CircuitBreaker:
    """Async-safe circuit breaker with atomic state transitions."""

    __slots__ = (
        "_threshold",
        "_timeout",
        "_state",
        "_failure_count",
        "_last_failure_time",
        "_lock",
        "_success_count",
        "_total_trips",
    )

    def __init__(self, threshold: int = 5, timeout: float = 60.0) -> None:
        self._threshold = threshold
        self._timeout = timeout
        self._state = CircuitState.CLOSED
        self._failure_count = 0
        self._success_count = 0
        self._last_failure_time = 0.0
        self._total_trips = 0
        self._lock = asyncio.Lock()

    @property
    def state(self) -> CircuitState:
        """Current circuit state (may auto-transition OPEN → HALF_OPEN)."""
        if self._state == CircuitState.OPEN:
            if time.monotonic() - self._last_failure_time >= self._timeout:
                return CircuitState.HALF_OPEN
        return self._state

    @property
    def is_available(self) -> bool:
        """Whether requests are currently allowed."""
        return self.state != CircuitState.OPEN

    @property
    def total_trips(self) -> int:
        """How many times the breaker has tripped open."""
        return self._total_trips

    async def on_success(self) -> None:
        """Record a successful request."""
        async with self._lock:
            current = self._effective_state()
            if current == CircuitState.HALF_OPEN:
                logger.info("Circuit breaker: HALF_OPEN → CLOSED (probe succeeded)")
                self._state = CircuitState.CLOSED
            self._failure_count = 0
            self._success_count += 1

    def _effective_state(self) -> CircuitState:
        """Compute state (call under lock)."""
        if self._state == CircuitState.OPEN:
            if time.monotonic() - self._last_failure_time >= self._timeout:
                return CircuitState.HALF_OPEN
        return self._state

    async def on_failure(self) -> None:
        """Record a failed request. May trip the breaker open."""
        async with self._lock:
            current = self._effective_state()  # Check BEFORE updating time
            self._failure_count += 1
            self._last_failure_time = time.monotonic()

            if current == CircuitState.HALF_OPEN:
                # Probe failed → back to OPEN with fresh timeout
                logger.warning("Circuit breaker: HALF_OPEN → OPEN (probe failed)")
                self._state = CircuitState.OPEN
                self._total_trips += 1
            elif self._failure_count >= self._threshold:
                logger.warning(
                    "Circuit breaker: CLOSED → OPEN (failures=%d >= threshold=%d)",
                    self._failure_count,
                    self._threshold,
                )
                self._state = CircuitState.OPEN
                self._total_trips += 1

    async def reset(self) -> None:
        """Force-reset to CLOSED state."""
        async with self._lock:
            self._state = CircuitState.CLOSED
            self._failure_count = 0
