"""Circuit breaker implementation for preventing cascading failures.

This module provides a production-grade circuit breaker pattern that protects
operations from cascading failures when downstream services are unavailable.
Follows the three-state model: CLOSED -> OPEN -> HALF_OPEN -> CLOSED/OPEN.

Thread Safety:
- Uses threading.RLock for reentrant locking to prevent race conditions
- All state mutations are protected by the same lock
- Atomic check-then-act operations to prevent inconsistent state
"""

import threading
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
from typing import Any, Literal

# Use standard logging, or assume loguru is configured globally if prefered.
# However, robin-commons instructions suggest consistent patterns.
# The original code imported logger from app.core.logging.config.
# We will change this to import from robin_commons.log
from robin_commons.log import logger


class CircuitBreakerState(Enum):
    """Circuit breaker states following traditional pattern."""

    CLOSED = "closed"  # Normal operation
    OPEN = "open"  # Failing fast
    HALF_OPEN = "half_open"  # Testing recovery


@dataclass(frozen=True)
class CircuitBreakerConfig:
    """Immutable configuration for circuit breaker behavior.

    Args:
        failure_threshold: Number of failures before opening circuit
        recovery_timeout_seconds: Time to wait before attempting recovery
        success_threshold: Number of successes needed to close circuit in HALF_OPEN
    """

    failure_threshold: int = 5
    recovery_timeout_seconds: int = 60
    success_threshold: int = 2


class CircuitBreakerError(Exception):
    """Raised when circuit breaker is in OPEN state preventing execution."""

    def __init__(
        self, state: CircuitBreakerState, next_attempt_at: datetime | None = None
    ) -> None:
        self.state = state
        self.next_attempt_at = next_attempt_at

        message = f"Circuit breaker is {state.value}"
        if next_attempt_at:
            message += f". Next attempt at {next_attempt_at.isoformat()}"

        super().__init__(message)


class CircuitBreaker:
    """Circuit breaker for protecting operations from cascading failures.

    Implements the circuit breaker pattern to prevent cascading failures
    when downstream services are unavailable. Follows the three-state
    model: CLOSED -> OPEN -> HALF_OPEN -> CLOSED/OPEN.
    """

    def __init__(self, config: CircuitBreakerConfig) -> None:
        """Initialize circuit breaker with configuration."""
        self._config = config
        self._lock = threading.RLock()

        # All mutable state protected by _lock
        self._state = CircuitBreakerState.CLOSED
        self._failure_count = 0
        self._success_count = 0
        self._next_attempt_at: datetime | None = None
        self._last_failure_time: datetime | None = None

    def can_execute(self) -> bool:
        """Check if circuit breaker allows operations."""
        with self._lock:
            if self._state == CircuitBreakerState.CLOSED:
                return True

            if self._state == CircuitBreakerState.OPEN:
                if self._next_attempt_at and datetime.now() >= self._next_attempt_at:
                    self._transition_to_half_open()
                    return True
                return False

            # HALF_OPEN state - allow single attempt
            return True

    def record_success(self) -> None:
        """Record successful operation execution."""
        with self._lock:
            logger.debug(
                "Circuit breaker recorded success",
                state=self._state.value,
                success_count=(
                    self._success_count + 1
                    if self._state == CircuitBreakerState.HALF_OPEN
                    else 0
                ),
            )

            if self._state == CircuitBreakerState.HALF_OPEN:
                self._success_count += 1
                if self._success_count >= self._config.success_threshold:
                    self._close_circuit()
            elif self._state == CircuitBreakerState.CLOSED:
                # Reset failure count on success in closed state
                self._failure_count = 0

    def record_failure(self) -> None:
        """Record failed operation execution."""
        with self._lock:
            self._failure_count += 1
            self._last_failure_time = datetime.now()

            logger.warning(
                "Circuit breaker recorded failure",
                state=self._state.value,
                failure_count=self._failure_count,
                threshold=self._config.failure_threshold,
            )

            if self._state == CircuitBreakerState.CLOSED:
                if self._failure_count >= self._config.failure_threshold:
                    self._open_circuit()
            elif self._state == CircuitBreakerState.HALF_OPEN:
                # Any failure in HALF_OPEN immediately opens circuit
                self._open_circuit()

    def get_state(self) -> CircuitBreakerState:
        """Get current circuit breaker state."""
        with self._lock:
            return self._state

    def get_failure_count(self) -> int:
        """Get current failure count."""
        with self._lock:
            return self._failure_count

    def get_next_attempt_time(self) -> datetime | None:
        """Get next attempt time when circuit is open."""
        with self._lock:
            return self._next_attempt_at

    def _transition_to_half_open(self) -> None:
        """Transition circuit breaker to HALF_OPEN state."""
        self._state = CircuitBreakerState.HALF_OPEN
        self._success_count = 0
        logger.info(
            "Circuit breaker transitioned to HALF_OPEN",
            failure_count=self._failure_count,
        )

    def _open_circuit(self) -> None:
        """Open circuit breaker due to failures."""
        self._state = CircuitBreakerState.OPEN
        self._success_count = 0
        self._next_attempt_at = datetime.now() + timedelta(
            seconds=self._config.recovery_timeout_seconds
        )

        logger.error(
            "Circuit breaker opened due to failures",
            failure_count=self._failure_count,
            threshold=self._config.failure_threshold,
            next_attempt=self._next_attempt_at.isoformat(),
        )

    def _close_circuit(self) -> None:
        """Close circuit breaker after successful recovery."""
        self._state = CircuitBreakerState.CLOSED
        self._failure_count = 0
        self._success_count = 0
        self._next_attempt_at = None
        self._last_failure_time = None

        logger.info(
            "Circuit breaker closed after successful recovery",
            success_threshold=self._config.success_threshold,
        )

    # ============================================
    # CONTEXT MANAGER SUPPORT (async with / with)
    # ============================================

    async def __aenter__(self) -> "CircuitBreaker":
        """Enter async context manager."""
        if not self.can_execute():
            raise CircuitBreakerError(
                state=self._state, next_attempt_at=self._next_attempt_at
            )
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: Any,
    ) -> Literal[False]:
        """Exit async context manager."""
        if exc_type is None:
            self.record_success()
        else:
            self.record_failure()
        return False

    def __enter__(self) -> "CircuitBreaker":
        """Enter synchronous context manager."""
        if not self.can_execute():
            raise CircuitBreakerError(
                state=self._state, next_attempt_at=self._next_attempt_at
            )
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: Any,
    ) -> Literal[False]:
        """Exit synchronous context manager."""
        if exc_type is None:
            self.record_success()
        else:
            self.record_failure()
        return False

    # ============================================
    # PROPERTIES FOR OBSERVABILITY
    # ============================================

    @property
    def is_open(self) -> bool:
        """Check if circuit breaker is currently open."""
        return self.get_state() == CircuitBreakerState.OPEN

    @property
    def state(self) -> str:
        """Get current state as string."""
        return self.get_state().value

    @property
    def failure_count(self) -> int:
        """Get current failure count."""
        return self.get_failure_count()

    @property
    def last_failure_time(self) -> datetime | None:
        """Get timestamp of last failure."""
        with self._lock:
            return self._last_failure_time
