"""
Provider failover manager.

Tracks provider health per symbol and recommends the best active source.

Policy:
- Each provider has a "health score" per symbol (0.0 – 1.0)
- Score decreases on gaps (heartbeat timeout) and error events
- Score increases on each successful trade/quote received
- When primary provider's score drops below threshold → switch to backup

Usage:
    failover = FailoverManager(primary="binance", backups=["bybit"])
    failover.record_event("binance", "BTCUSDT")  # bumps score
    failover.record_gap("binance", "BTCUSDT")    # decreases score
    best = failover.get_best_provider("BTCUSDT")  # → "binance" or "bybit"
"""
from __future__ import annotations

import logging
import time
from dataclasses import dataclass

logger = logging.getLogger(__name__)


@dataclass
class ProviderHealth:
    """Health tracker for one provider+symbol pair."""

    score: float = 1.0
    event_count: int = 0
    gap_count: int = 0
    last_event_ts: float = 0.0
    last_gap_ts: float = 0.0

    def record_event(self) -> None:
        """Bump health score on successful event."""
        self.event_count += 1
        self.last_event_ts = time.monotonic()
        # Recover towards 1.0 gradually
        self.score = min(1.0, self.score + 0.01)

    def record_gap(self) -> None:
        """Decrease health score on gap/timeout."""
        self.gap_count += 1
        self.last_gap_ts = time.monotonic()
        self.score = max(0.0, self.score - 0.2)


class FailoverManager:
    """
    Tracks provider health and recommends best source per symbol.
    """

    def __init__(
        self,
        primary: str,
        backups: list[str] | None = None,
        switch_threshold: float = 0.3,
        recovery_threshold: float = 0.7,
    ) -> None:
        self._primary = primary
        self._backups = backups or []
        self._all_providers = [primary] + self._backups
        self._switch_threshold = switch_threshold
        self._recovery_threshold = recovery_threshold

        # provider → symbol → ProviderHealth
        self._health: dict[str, dict[str, ProviderHealth]] = {}

        # symbol → currently active provider
        self._active: dict[str, str] = {}

    def _get_health(self, provider: str, symbol: str) -> ProviderHealth:
        """Get or create health tracker."""
        if provider not in self._health:
            self._health[provider] = {}
        if symbol not in self._health[provider]:
            self._health[provider][symbol] = ProviderHealth()
        return self._health[provider][symbol]

    def record_event(self, provider: str, symbol: str) -> None:
        """Record a successful event from provider for symbol."""
        self._get_health(provider, symbol).record_event()

    def record_gap(self, provider: str, symbol: str) -> None:
        """Record a gap/timeout for provider+symbol."""
        h = self._get_health(provider, symbol)
        h.record_gap()
        logger.warning(
            "failover.gap_recorded",
            extra={
                "provider": provider,
                "symbol": symbol,
                "score": f"{h.score:.2f}",
                "gaps": h.gap_count,
            },
        )

    def get_best_provider(self, symbol: str) -> str:
        """
        Return the currently recommended provider for this symbol.

        Logic:
        1. If active provider score >= switch_threshold → keep it
        2. If active provider drops below → switch to healthiest backup
        3. If active provider recovers above recovery_threshold → switch back to primary
        """
        current = self._active.get(symbol, self._primary)
        current_health = self._get_health(current, symbol)

        # Check if current provider is degraded
        if current_health.score < self._switch_threshold:
            # Find best backup
            best_provider = current
            best_score = current_health.score

            for p in self._all_providers:
                if p == current:
                    continue
                h = self._get_health(p, symbol)
                if h.score > best_score:
                    best_provider = p
                    best_score = h.score

            if best_provider != current:
                logger.warning(
                    "failover.switching",
                    extra={
                        "symbol": symbol,
                        "from": current,
                        "to": best_provider,
                        "old_score": f"{current_health.score:.2f}",
                        "new_score": f"{best_score:.2f}",
                    },
                )
                self._active[symbol] = best_provider
                return best_provider

        # Check if primary has recovered and we're on a backup
        if current != self._primary:
            primary_health = self._get_health(self._primary, symbol)
            if primary_health.score >= self._recovery_threshold:
                logger.info(
                    "failover.returning_to_primary",
                    extra={
                        "symbol": symbol,
                        "primary_score": f"{primary_health.score:.2f}",
                    },
                )
                self._active[symbol] = self._primary
                return self._primary

        self._active[symbol] = current
        return current

    def get_status(self) -> dict:
        """Return full failover status for monitoring."""
        status = {}
        for provider, symbols in self._health.items():
            for symbol, health in symbols.items():
                key = f"{provider}/{symbol}"
                status[key] = {
                    "score": round(health.score, 2),
                    "events": health.event_count,
                    "gaps": health.gap_count,
                    "active": self._active.get(symbol) == provider,
                }
        return status
