"""Fund-level risk controls: drawdown circuit breaker, correlation alerts."""

from __future__ import annotations

import logging
import time
from collections import deque
from dataclasses import dataclass, field
from typing import Any

from horizon.fund._types import FundConfig, NAVSnapshot

logger = logging.getLogger("horizon.fund.risk")


@dataclass
class RiskAlert:
    """A risk alert raised by the fund risk monitor."""
    timestamp: float
    level: str  # "warning", "critical"
    category: str  # "drawdown", "correlation", "strategy_failure"
    message: str
    details: dict[str, Any] = field(default_factory=dict)


class FundRiskMonitor:
    """Monitors fund-level risk and triggers circuit breakers.

    Checks:
    - Fund-wide drawdown vs. limit
    - Per-strategy drawdown vs. limit
    - Strategy failure count
    """

    def __init__(self, config: FundConfig) -> None:
        self._config = config
        self._alerts: deque[RiskAlert] = deque(maxlen=10_000)
        self._fund_kill_switch = False
        self._paused_strategies: set[str] = set()
        self._last_recovered: list[str] = []
        self._kill_switch_just_recovered: bool = False

    @property
    def fund_kill_switch_active(self) -> bool:
        return self._fund_kill_switch

    @property
    def paused_strategies(self) -> set[str]:
        return set(self._paused_strategies)

    @property
    def last_recovered_strategies(self) -> list[str]:
        """Strategy names that were auto-unpaused in the most recent check()."""
        return list(self._last_recovered)

    @property
    def kill_switch_just_recovered(self) -> bool:
        """Whether the kill switch was auto-reset in the most recent check()."""
        return self._kill_switch_just_recovered

    def check(
        self,
        nav: NAVSnapshot,
        strategy_drawdowns: dict[str, float],
        failed_strategies: list[str],
    ) -> list[RiskAlert]:
        """Run all risk checks, return new alerts.

        Args:
            nav: Current fund NAV snapshot.
            strategy_drawdowns: Mapping of strategy_name -> drawdown_pct.
            failed_strategies: Names of strategies in FAILED state.

        Returns:
            List of new alerts generated this check.
        """
        new_alerts: list[RiskAlert] = []
        now = time.time()

        # 1. Fund-wide drawdown check
        if nav.drawdown_pct >= self._config.max_fund_drawdown_pct:
            alert = RiskAlert(
                timestamp=now,
                level="critical",
                category="drawdown",
                message=(
                    f"Fund drawdown {nav.drawdown_pct:.1f}% exceeds limit "
                    f"{self._config.max_fund_drawdown_pct:.1f}%"
                ),
                details={
                    "drawdown_pct": nav.drawdown_pct,
                    "limit_pct": self._config.max_fund_drawdown_pct,
                    "nav": nav.total_nav,
                    "hwm": nav.high_water_mark,
                },
            )
            new_alerts.append(alert)
            self._fund_kill_switch = True
            logger.critical(
                "FUND KILL SWITCH: drawdown %.1f%% >= limit %.1f%%",
                nav.drawdown_pct, self._config.max_fund_drawdown_pct,
            )
        elif nav.drawdown_pct >= self._config.max_fund_drawdown_pct * 0.8:
            alert = RiskAlert(
                timestamp=now,
                level="warning",
                category="drawdown",
                message=(
                    f"Fund drawdown {nav.drawdown_pct:.1f}% approaching limit "
                    f"{self._config.max_fund_drawdown_pct:.1f}%"
                ),
                details={
                    "drawdown_pct": nav.drawdown_pct,
                    "limit_pct": self._config.max_fund_drawdown_pct,
                },
            )
            new_alerts.append(alert)

        # 2. Per-strategy drawdown check
        for name, dd_pct in strategy_drawdowns.items():
            if dd_pct >= self._config.max_strategy_drawdown_pct:
                alert = RiskAlert(
                    timestamp=now,
                    level="critical",
                    category="drawdown",
                    message=(
                        f"Strategy '{name}' drawdown {dd_pct:.1f}% exceeds limit "
                        f"{self._config.max_strategy_drawdown_pct:.1f}%"
                    ),
                    details={
                        "strategy": name,
                        "drawdown_pct": dd_pct,
                        "limit_pct": self._config.max_strategy_drawdown_pct,
                    },
                )
                new_alerts.append(alert)
                self._paused_strategies.add(name)
                logger.warning(
                    "Strategy '%s' paused: drawdown %.1f%% >= limit %.1f%%",
                    name, dd_pct, self._config.max_strategy_drawdown_pct,
                )

        # 3. Multiple strategy failure check
        if len(failed_strategies) >= self._config.max_failed_strategies:
            alert = RiskAlert(
                timestamp=now,
                level="critical",
                category="strategy_failure",
                message=f"{len(failed_strategies)} strategies have failed",
                details={"failed": failed_strategies},
            )
            new_alerts.append(alert)

        # 4. Auto-unpause: if strategy drawdown recovers to below 50% of limit, unpause
        recovered: list[str] = []
        for name in list(self._paused_strategies):
            dd = strategy_drawdowns.get(name, 0.0)
            if dd < self._config.max_strategy_drawdown_pct * 0.5:
                recovered.append(name)
        for name in recovered:
            self._paused_strategies.discard(name)
            logger.info(
                "Strategy '%s' auto-unpaused: drawdown recovered below %.1f%%",
                name, self._config.max_strategy_drawdown_pct * 0.5,
            )
        self._last_recovered = recovered

        # 5. Kill switch recovery: auto-reset if drawdown drops below recovery threshold
        self._kill_switch_just_recovered = False
        if self._fund_kill_switch and nav.drawdown_pct < self._config.kill_switch_recovery_drawdown_pct:
            self._fund_kill_switch = False
            self._kill_switch_just_recovered = True
            logger.info(
                "Kill switch auto-reset: drawdown %.1f%% < recovery threshold %.1f%%",
                nav.drawdown_pct, self._config.kill_switch_recovery_drawdown_pct,
            )

        self._alerts.extend(new_alerts)
        return new_alerts

    def clear_strategy_pause(self, name: str) -> None:
        """Remove a strategy from the paused set (manual override)."""
        self._paused_strategies.discard(name)

    def reset_kill_switch(self) -> None:
        """Reset the fund kill switch (manual override)."""
        self._fund_kill_switch = False
        logger.info("Fund kill switch reset by operator")

    def recent_alerts(self, limit: int = 50) -> list[RiskAlert]:
        """Return recent risk alerts."""
        return list(self._alerts)[-limit:]
