"""Provider 熔断器（CircuitBreaker）.

设计依据 02-architecture-design.md §13.2：
  - 3 次连续失败触发熔断
  - 熔断 60s 后进入半开状态
  - 半开状态允许 1 次试探调用，成功则关闭熔断，失败则重新熔断

状态机：
  CLOSED → (failures >= threshold) → OPEN
  OPEN → (after cooldown) → HALF_OPEN
  HALF_OPEN → success → CLOSED
  HALF_OPEN → failure → OPEN
"""

import time
from enum import StrEnum

import structlog

logger = structlog.get_logger()


class CircuitState(StrEnum):
    """熔断器状态."""

    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"


class CircuitBreaker:
    """单 Provider 熔断器.

    线程安全由 asyncio 单线程模型保证（FastAPI 事件循环）。
    如需跨进程共享，应迁移到 Redis 实现。
    """

    def __init__(
        self,
        failure_threshold: int = 3,
        cooldown_seconds: float = 60.0,
        half_open_max_calls: int = 1,
    ) -> None:
        self._failure_threshold = failure_threshold
        self._cooldown_seconds = cooldown_seconds
        self._half_open_max_calls = half_open_max_calls
        # 每个 provider 独立状态
        self._states: dict[str, CircuitState] = {}
        self._failure_counts: dict[str, int] = {}
        self._last_failure_times: dict[str, float] = {}
        self._half_open_calls: dict[str, int] = {}

    def get_state(self, provider_name: str) -> CircuitState:
        """获取 Provider 当前熔断状态（含自动转换 OPEN → HALF_OPEN）."""
        state = self._states.get(provider_name, CircuitState.CLOSED)
        if state == CircuitState.OPEN:
            last_failure = self._last_failure_times.get(provider_name, 0)
            if time.monotonic() - last_failure >= self._cooldown_seconds:
                self._states[provider_name] = CircuitState.HALF_OPEN
                self._half_open_calls[provider_name] = 0
                logger.info(
                    "circuit_breaker_half_open",
                    provider=provider_name,
                    cooldown_seconds=self._cooldown_seconds,
                )
                return CircuitState.HALF_OPEN
        return self._states.get(provider_name, CircuitState.CLOSED)

    def is_closed(self, provider_name: str) -> bool:
        """Provider 是否允许调用（CLOSED 或 HALF_OPEN 未超限）."""
        state = self.get_state(provider_name)
        if state == CircuitState.CLOSED:
            return True
        if state == CircuitState.HALF_OPEN:
            calls = self._half_open_calls.get(provider_name, 0)
            if calls < self._half_open_max_calls:
                self._half_open_calls[provider_name] = calls + 1
                return True
            return False
        return False

    def record_success(self, provider_name: str) -> None:
        """记录调用成功（重置失败计数，HALF_OPEN → CLOSED）."""
        self._failure_counts[provider_name] = 0
        old_state = self._states.get(provider_name, CircuitState.CLOSED)
        if old_state == CircuitState.HALF_OPEN:
            self._states[provider_name] = CircuitState.CLOSED
            logger.info(
                "circuit_breaker_recovered",
                provider=provider_name,
            )

    def record_failure(self, provider_name: str) -> None:
        """记录调用失败（累加计数，达阈值则 OPEN）."""
        self._failure_counts[provider_name] = self._failure_counts.get(provider_name, 0) + 1
        self._last_failure_times[provider_name] = time.monotonic()
        state = self._states.get(provider_name, CircuitState.CLOSED)

        if state == CircuitState.HALF_OPEN:
            # 半开状态失败，立即重新熔断
            self._states[provider_name] = CircuitState.OPEN
            logger.warning(
                "circuit_breaker_reopened",
                provider=provider_name,
            )
            return

        failures = self._failure_counts[provider_name]
        if failures >= self._failure_threshold:
            self._states[provider_name] = CircuitState.OPEN
            logger.warning(
                "circuit_breaker_opened",
                provider=provider_name,
                failures=failures,
                threshold=self._failure_threshold,
            )

    def reset(self, provider_name: str) -> None:
        """重置 Provider 熔断状态（手动恢复用）."""
        self._states.pop(provider_name, None)
        self._failure_counts.pop(provider_name, None)
        self._last_failure_times.pop(provider_name, None)
        self._half_open_calls.pop(provider_name, None)

    def status(self) -> dict[str, dict[str, object]]:
        """所有 Provider 熔断状态快照（/readyz 用）."""
        result: dict[str, dict[str, object]] = {}
        all_providers = set(self._states) | set(self._failure_counts)
        for name in all_providers:
            result[name] = {
                "state": self.get_state(name).value,
                "failures": self._failure_counts.get(name, 0),
                "last_failure_at": self._last_failure_times.get(name),
            }
        return result
