"""Health monitoring and circuit breaker mixin for the Cascade Router.

Provides failure tracking, circuit breaker state transitions,
and quota-based cloud provider gating.
"""

from __future__ import annotations

import logging
import time
from datetime import UTC, datetime

from .models import CircuitState, Provider, ProviderStatus

logger = logging.getLogger(__name__)

# Quota monitor — optional, degrades gracefully if unavailable
try:
    from infrastructure.claude_quota import QuotaMonitor, get_quota_monitor

    _quota_monitor: QuotaMonitor | None = get_quota_monitor()
except Exception as _exc:  # pragma: no cover
    logger.debug("Quota monitor not available: %s", _exc)
    _quota_monitor = None


class HealthMixin:
    """Mixin providing health tracking, circuit breaker, and quota checks.

    Expects the consuming class to have:
      - self.config: RouterConfig
      - self.providers: list[Provider]
    """

    def _record_success(self, provider: Provider, latency_ms: float) -> None:
        """Record a successful request."""
        provider.metrics.total_requests += 1
        provider.metrics.successful_requests += 1
        provider.metrics.total_latency_ms += latency_ms
        provider.metrics.last_request_time = datetime.now(UTC).isoformat()
        provider.metrics.consecutive_failures = 0

        # Close circuit breaker if half-open
        if provider.circuit_state == CircuitState.HALF_OPEN:
            provider.half_open_calls += 1
            if provider.half_open_calls >= self.config.circuit_breaker_half_open_max_calls:
                self._close_circuit(provider)

        # Update status based on error rate
        if provider.metrics.error_rate < 0.1:
            provider.status = ProviderStatus.HEALTHY
        elif provider.metrics.error_rate < 0.3:
            provider.status = ProviderStatus.DEGRADED

    def _record_failure(self, provider: Provider) -> None:
        """Record a failed request."""
        provider.metrics.total_requests += 1
        provider.metrics.failed_requests += 1
        provider.metrics.last_error_time = datetime.now(UTC).isoformat()
        provider.metrics.consecutive_failures += 1

        # Check if we should open circuit breaker
        if provider.metrics.consecutive_failures >= self.config.circuit_breaker_failure_threshold:
            self._open_circuit(provider)

        # Update status
        if provider.metrics.error_rate > 0.3:
            provider.status = ProviderStatus.DEGRADED
        if provider.metrics.error_rate > 0.5:
            provider.status = ProviderStatus.UNHEALTHY

    def _open_circuit(self, provider: Provider) -> None:
        """Open the circuit breaker for a provider."""
        provider.circuit_state = CircuitState.OPEN
        provider.circuit_opened_at = time.time()
        provider.status = ProviderStatus.UNHEALTHY
        logger.warning("Circuit breaker OPEN for %s", provider.name)

    def _can_close_circuit(self, provider: Provider) -> bool:
        """Check if circuit breaker can transition to half-open."""
        if provider.circuit_opened_at is None:
            return False
        elapsed = time.time() - provider.circuit_opened_at
        return elapsed >= self.config.circuit_breaker_recovery_timeout

    def _close_circuit(self, provider: Provider) -> None:
        """Close the circuit breaker (provider healthy again)."""
        provider.circuit_state = CircuitState.CLOSED
        provider.circuit_opened_at = None
        provider.half_open_calls = 0
        provider.metrics.consecutive_failures = 0
        provider.status = ProviderStatus.HEALTHY
        logger.info("Circuit breaker CLOSED for %s", provider.name)

    def _is_provider_available(self, provider: Provider) -> bool:
        """Check if a provider should be tried (enabled + circuit breaker)."""
        if not provider.enabled:
            logger.debug("Skipping %s (disabled)", provider.name)
            return False

        if provider.status == ProviderStatus.UNHEALTHY:
            if self._can_close_circuit(provider):
                provider.circuit_state = CircuitState.HALF_OPEN
                provider.half_open_calls = 0
                logger.info("Circuit breaker half-open for %s", provider.name)
            else:
                logger.debug("Skipping %s (circuit open)", provider.name)
                return False

        return True

    def _quota_allows_cloud(self, provider: Provider) -> bool:
        """Check quota before routing to a cloud provider.

        Uses the metabolic protocol via select_model(): cloud calls are only
        allowed when the quota monitor recommends a cloud model (BURST tier).
        Returns True (allow cloud) if quota monitor is unavailable or returns None.
        """
        if _quota_monitor is None:
            return True
        try:
            suggested = _quota_monitor.select_model("high")
            # Cloud is allowed only when select_model recommends the cloud model
            allows = suggested == "claude-sonnet-4-6"
            if not allows:
                status = _quota_monitor.check()
                tier = status.recommended_tier.value if status else "unknown"
                logger.info(
                    "Metabolic protocol: %s tier — downshifting %s to local (%s)",
                    tier,
                    provider.name,
                    suggested,
                )
            return allows
        except Exception as exc:
            logger.warning("Quota check failed, allowing cloud: %s", exc)
            return True
