"""Regime-conditional risk, portfolio Greeks, and tail risk analytics."""

from __future__ import annotations

import logging
import math
import threading
import time
from collections import deque
from dataclasses import dataclass, field

from horizon._horizon import cornish_fisher_var, cornish_fisher_cvar, prediction_greeks

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

# ---------------------------------------------------------------------------
# Regime risk profiles
# ---------------------------------------------------------------------------

_VOL_CRISIS_THRESHOLD = 0.04  # annualized vol above which volatile -> crisis


@dataclass
class RegimeRiskParams:
    """Risk parameters conditional on the current market regime."""

    position_scale: float = 1.0
    var_confidence: float = 0.95
    spread_multiplier: float = 1.0


_REGIME_PROFILES: dict[str, RegimeRiskParams] = {
    "quiet": RegimeRiskParams(position_scale=1.0, var_confidence=0.95, spread_multiplier=1.0),
    "trending_up": RegimeRiskParams(position_scale=1.0, var_confidence=0.95, spread_multiplier=1.0),
    "trending_down": RegimeRiskParams(position_scale=1.0, var_confidence=0.95, spread_multiplier=1.0),
    "mean_reverting": RegimeRiskParams(position_scale=1.0, var_confidence=0.95, spread_multiplier=1.0),
    "volatile": RegimeRiskParams(position_scale=0.7, var_confidence=0.99, spread_multiplier=1.3),
    "crisis": RegimeRiskParams(position_scale=0.5, var_confidence=0.99, spread_multiplier=1.5),
}


# ---------------------------------------------------------------------------
# Report dataclasses
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class TailRiskReport:
    """Tail risk metrics combining historical and Cornish-Fisher approaches."""

    historical_var: float
    historical_cvar: float
    cf_var: float
    cf_cvar: float
    skewness: float
    kurtosis: float
    tail_ratio: float


@dataclass(frozen=True)
class PortfolioGreeksReport:
    """Aggregated prediction-market Greeks across all positions."""

    total_delta: float
    total_gamma: float
    total_theta: float
    total_vega: float
    per_strategy: dict[str, dict[str, float]]
    timestamp: float


@dataclass(frozen=True)
class DynamicLimits:
    """Risk limits adjusted for the current regime."""

    position_scale: float
    max_position_pct: float
    var_limit: float
    spread_multiplier: float
    regime: str


# ---------------------------------------------------------------------------
# Pure helpers
# ---------------------------------------------------------------------------


def compute_skewness(values: list[float]) -> float:
    """Sample skewness (Fisher's definition)."""
    n = len(values)
    if n < 3:
        return 0.0
    mean = sum(values) / n
    m2 = sum((v - mean) ** 2 for v in values) / n
    m3 = sum((v - mean) ** 3 for v in values) / n
    if m2 < 1e-18:
        return 0.0
    return m3 / (m2 ** 1.5)


def compute_kurtosis(values: list[float]) -> float:
    """Excess kurtosis (Fisher's definition, normal = 0)."""
    n = len(values)
    if n < 4:
        return 0.0
    mean = sum(values) / n
    m2 = sum((v - mean) ** 2 for v in values) / n
    m4 = sum((v - mean) ** 4 for v in values) / n
    if m2 < 1e-18:
        return 0.0
    return (m4 / (m2 ** 2)) - 3.0


# ---------------------------------------------------------------------------
# Main class
# ---------------------------------------------------------------------------


class RiskAnalytics:
    """Regime-conditional risk analytics combining tail risk, Greeks, and dynamic limits.

    Wraps Rust Cornish-Fisher VaR/CVaR and prediction Greeks with a
    regime-aware layer that scales position limits, VaR budgets, and
    spread multipliers based on current market conditions.
    """

    def __init__(
        self,
        base_position_pct: float = 0.05,
        base_var_limit: float = 0.10,
    ) -> None:
        self._regime_profiles: dict[str, RegimeRiskParams] = dict(_REGIME_PROFILES)
        self._base_position_pct = base_position_pct
        self._base_var_limit = base_var_limit
        self._return_history: deque[float] = deque(maxlen=500)
        self._greeks_cache: PortfolioGreeksReport | None = None
        self._lock = threading.Lock()

    # ------------------------------------------------------------------
    # Dynamic limits
    # ------------------------------------------------------------------

    def dynamic_limits(self, regime: str) -> DynamicLimits:
        """Compute risk limits adjusted for the given regime.

        If the regime contains 'volatile' and the recent realized
        volatility exceeds the crisis threshold, crisis parameters
        are used instead.

        Args:
            regime: Current regime label from RegimeDetector.

        Returns:
            DynamicLimits with scaled position size, VaR, and spread.
        """
        with self._lock:
            history = list(self._return_history)

        effective_regime = regime

        # Escalate volatile -> crisis when realized vol is extreme
        if "volatile" in regime and len(history) >= 20:
            vol = _std(history[-50:]) if len(history) >= 50 else _std(history)
            if vol > _VOL_CRISIS_THRESHOLD:
                effective_regime = "crisis"

        params = self._regime_profiles.get(effective_regime, self._regime_profiles["quiet"])

        return DynamicLimits(
            position_scale=params.position_scale,
            max_position_pct=self._base_position_pct * params.position_scale,
            var_limit=self._base_var_limit * params.position_scale,
            spread_multiplier=params.spread_multiplier,
            regime=effective_regime,
        )

    # ------------------------------------------------------------------
    # Tail risk
    # ------------------------------------------------------------------

    def tail_risk(self, returns: list[float] | None = None) -> TailRiskReport | None:
        """Compute tail risk report using historical and Cornish-Fisher methods.

        Args:
            returns: Optional return series. If *None*, uses internal history.

        Returns:
            TailRiskReport or None if fewer than 20 observations.
        """
        if returns is None:
            with self._lock:
                returns = list(self._return_history)

        if len(returns) < 20:
            return None

        # Historical VaR / CVaR (percentile-based)
        sorted_rets = sorted(returns)
        n = len(sorted_rets)
        var_idx = max(0, int(0.05 * n) - 1)
        hist_var = abs(sorted_rets[var_idx])
        tail_slice = sorted_rets[: max(1, int(0.05 * n))]
        hist_cvar = abs(sum(tail_slice) / len(tail_slice))

        # Cornish-Fisher VaR / CVaR (via Rust)
        cf_var_val = cornish_fisher_var(returns, confidence=0.95)
        cf_cvar_val = cornish_fisher_cvar(returns, confidence=0.95)

        # Higher moments
        skew = compute_skewness(returns)
        kurt = compute_kurtosis(returns)

        # Tail ratio: |95th percentile / 5th percentile|
        p95_idx = min(n - 1, int(0.95 * n))
        p05_idx = max(0, int(0.05 * n))
        p95 = sorted_rets[p95_idx]
        p05 = sorted_rets[p05_idx]
        tail_ratio = abs(p95 / p05) if abs(p05) > 1e-12 else 0.0

        return TailRiskReport(
            historical_var=hist_var,
            historical_cvar=hist_cvar,
            cf_var=cf_var_val,
            cf_cvar=cf_cvar_val,
            skewness=skew,
            kurtosis=kurt,
            tail_ratio=tail_ratio,
        )

    # ------------------------------------------------------------------
    # Portfolio Greeks
    # ------------------------------------------------------------------

    def portfolio_greeks(self, positions: list[dict]) -> PortfolioGreeksReport:
        """Aggregate prediction-market Greeks across positions.

        Args:
            positions: List of dicts, each with keys:
                - strategy_name (str)
                - price (float)
                - size (float)
                - is_yes (bool)
                - t_hours (float, optional, default 24.0)
                - vol (float, optional, default 0.2)

        Returns:
            PortfolioGreeksReport with totals and per-strategy breakdown.
        """
        total_delta = 0.0
        total_gamma = 0.0
        total_theta = 0.0
        total_vega = 0.0
        per_strategy: dict[str, dict[str, float]] = {}

        for pos in positions:
            name = pos.get("strategy_name", "unknown")
            price = pos.get("price", 0.5)
            size = pos.get("size", 0.0)
            is_yes = pos.get("is_yes", True)
            t_hours = pos.get("t_hours", 24.0)
            vol = pos.get("vol", 0.2)

            greeks = prediction_greeks(
                price=price,
                size=size,
                is_yes=is_yes,
                t_hours=t_hours,
                vol=vol,
            )

            total_delta += greeks.delta
            total_gamma += greeks.gamma
            total_theta += greeks.theta
            total_vega += greeks.vega

            if name not in per_strategy:
                per_strategy[name] = {"delta": 0.0, "gamma": 0.0, "theta": 0.0, "vega": 0.0}

            per_strategy[name]["delta"] += greeks.delta
            per_strategy[name]["gamma"] += greeks.gamma
            per_strategy[name]["theta"] += greeks.theta
            per_strategy[name]["vega"] += greeks.vega

        report = PortfolioGreeksReport(
            total_delta=total_delta,
            total_gamma=total_gamma,
            total_theta=total_theta,
            total_vega=total_vega,
            per_strategy=per_strategy,
            timestamp=time.time(),
        )

        with self._lock:
            self._greeks_cache = report

        return report

    # ------------------------------------------------------------------
    # Return tracking
    # ------------------------------------------------------------------

    def update_returns(self, ret: float) -> None:
        """Append a single return observation to the internal history."""
        if not math.isfinite(ret):
            return
        with self._lock:
            self._return_history.append(ret)


# ---------------------------------------------------------------------------
# Module-level helpers
# ---------------------------------------------------------------------------


def _std(xs: list[float]) -> float:
    """Population standard deviation."""
    n = len(xs)
    if n < 2:
        return 0.0
    m = sum(xs) / n
    return math.sqrt(sum((x - m) ** 2 for x in xs) / n)
