from __future__ import annotations

import logging
import time
from typing import Any

from app.core.ai.domain.models import AiProviderType

logger = logging.getLogger("dscons.ai.circuit_breaker")


class ProviderCircuitBreaker:
    """Enterprise Circuit Breaker to isolate failing AI providers and prevent cascading timeouts."""

    def __init__(
        self,
        failure_threshold: int = 3,
        recovery_time_seconds: float = 60.0,
    ) -> None:
        self._failure_threshold = failure_threshold
        self._recovery_time = recovery_time_seconds
        self._failure_counts: dict[AiProviderType, int] = {
            p: 0 for p in AiProviderType
        }
        self._last_failure_time: dict[AiProviderType, float] = {
            p: 0.0 for p in AiProviderType
        }
        self._circuit_open: dict[AiProviderType, bool] = {
            p: False for p in AiProviderType
        }

    def can_attempt(self, provider: AiProviderType) -> bool:
        """Determines if requests can be sent to the given provider."""
        if not self._circuit_open.get(provider, False):
            return True

        # Check if recovery time has elapsed to attempt half-open probe
        elapsed = time.time() - self._last_failure_time.get(provider, 0.0)
        if elapsed >= self._recovery_time:
            logger.info(
                "[CIRCUIT_BREAKER] Probing provider %s after %.1fs recovery cooldown.",
                provider.value,
                elapsed,
            )
            return True

        return False

    def record_success(self, provider: AiProviderType) -> None:
        """Records a successful operation, closing the circuit."""
        if self._circuit_open.get(provider, False):
            logger.info("[CIRCUIT_BREAKER] Provider %s recovered. Circuit CLOSED.", provider.value)
        self._failure_counts[provider] = 0
        self._circuit_open[provider] = False

    def record_failure(
        self, provider: AiProviderType, error: Exception | str
    ) -> None:
        """Records an error (503, 429, timeout) and trips circuit if threshold exceeded."""
        now = time.time()
        self._last_failure_time[provider] = now
        count = self._failure_counts.get(provider, 0) + 1
        self._failure_counts[provider] = count

        err_msg = str(error)
        is_hard_failure = any(
            code in err_msg for code in ("503", "429", "RESOURCE_EXHAUSTED", "UNAVAILABLE")
        )

        if count >= self._failure_threshold or is_hard_failure:
            self._circuit_open[provider] = True
            logger.warning(
                "[CIRCUIT_BREAKER] Tripped circuit OPEN for provider %s (Consecutive failures: %d, reason: %s). Cooldown: %.0fs",
                provider.value,
                count,
                err_msg[:120],
                self._recovery_time,
            )
