"""
Risk analytics module.

Value at Risk, Conditional VaR, drawdown analysis, correlation,
and composite risk ratios. Builds on common/metrics.py for
Sharpe/Sortino/max_drawdown which are re-exported here.

All functions accept equity curves as either plain lists of floats
or lists of {t: timestamp, v: equity_value} dicts.
"""

import math
import numpy as np


# ═══════════════════════════════════════════════════════════
# Helpers
# ═══════════════════════════════════════════════════════════

def _to_values(curve):
    """Normalise an equity curve to a list of floats."""
    if not curve:
        return []
    if isinstance(curve[0], dict):
        return [p["v"] for p in curve]
    return [float(v) for v in curve]


def _daily_returns(curve):
    """Compute daily log returns from an equity curve."""
    vals = _to_values(curve)
    if len(vals) < 2:
        return []
    return [math.log(vals[i] / vals[i - 1]) for i in range(1, len(vals))]


# ═══════════════════════════════════════════════════════════
# Value at Risk & Conditional VaR
# ═══════════════════════════════════════════════════════════

def var_95(daily_returns):
    """
    95% historical Value at Risk.

    Returns a *positive* number representing the loss threshold
    (e.g. 0.02 means "we are 95% confident daily loss won't exceed 2%").
    """
    if len(daily_returns) < 5:
        return 0.0
    sorted_ret = sorted(daily_returns)
    idx = max(0, int(len(sorted_ret) * 0.05))
    var = sorted_ret[idx]
    return -min(var, 0.0)  # return positive loss magnitude


def cvar_95(daily_returns):
    """
    95% Conditional Value at Risk (Expected Shortfall).

    Average loss *beyond* the VaR threshold. Returns a positive number.
    """
    if len(daily_returns) < 5:
        return 0.0
    sorted_ret = sorted(daily_returns)
    cutoff = max(0, int(len(sorted_ret) * 0.05))
    tail = [r for r in sorted_ret[:cutoff + 1] if r < 0]
    if not tail:
        return 0.0
    return -np.mean(tail)


def var_95_from_equity(equity_curve):
    """Convenience: VaR computed directly from an equity curve."""
    return var_95(_daily_returns(equity_curve))


def cvar_95_from_equity(equity_curve):
    """Convenience: CVaR computed directly from an equity curve."""
    return cvar_95(_daily_returns(equity_curve))


# ═══════════════════════════════════════════════════════════
# Drawdown
# ═══════════════════════════════════════════════════════════

def max_drawdown(equity_curve):
    """
    Maximum drawdown from an equity curve (peak-to-trough).

    Returns a positive fraction (0.25 = 25% max DD).
    Accepts list of floats or list of {t, v} dicts.
    """
    vals = _to_values(equity_curve)
    if not vals:
        return 0.0
    peak = vals[0]
    worst = 0.0
    for v in vals:
        if v > peak:
            peak = v
        if peak > 0:
            dd = (peak - v) / peak
            worst = max(worst, dd)
    return worst


# ═══════════════════════════════════════════════════════════
# Ratios
# ═══════════════════════════════════════════════════════════

def sharpe(returns, rf=0.0, periods=365):
    """
    Annualised Sharpe ratio.

    `returns` should be a list of daily log returns (floats).
    """
    if len(returns) < 2:
        return 0.0
    excess = np.mean(returns) - rf
    std = np.std(returns, ddof=1)
    if std <= 0:
        return 0.0
    return (excess / std) * math.sqrt(periods)


def sortino(returns, rf=0.0, periods=365):
    """
    Annualised Sortino ratio (downside deviation only).
    """
    if len(returns) < 2:
        return 0.0
    excess = np.mean(returns) - rf
    downside = [r for r in returns if r < 0]
    d_std = np.std(downside, ddof=1) if downside else 0.0
    if d_std <= 0:
        return 0.0
    return (excess / d_std) * math.sqrt(periods)


def calmar_ratio(returns, max_dd):
    """
    Calmar ratio = annualised return / maximum drawdown.

    `returns` is a list of daily log returns.
    `max_dd` is a positive fraction (0.25 = 25% drawdown).
    """
    if len(returns) < 2 or max_dd <= 0:
        return 0.0
    ann_return = np.mean(returns) * 365
    return ann_return / max_dd


def sharpe_from_equity(equity_curve):
    """Sharpe ratio computed from an equity curve."""
    return sharpe(_daily_returns(equity_curve))


def sortino_from_equity(equity_curve):
    """Sortino ratio computed from an equity curve."""
    return sortino(_daily_returns(equity_curve))


def calmar_from_equity(equity_curve):
    """Calmar ratio computed from an equity curve."""
    dr = _daily_returns(equity_curve)
    dd = max_drawdown(equity_curve)
    return calmar_ratio(dr, dd)


# ═══════════════════════════════════════════════════════════
# Correlation
# ═══════════════════════════════════════════════════════════

def correlation_matrix(strategy_returns_dict):
    """
    Pearson correlation matrix between strategies.

    Args:
        strategy_returns_dict: {name: [daily_log_returns], ...}

    Returns:
        {name: {name: float, ...}, ...}
        or empty dict if fewer than 2 strategies.
    """
    names = list(strategy_returns_dict.keys())
    if len(names) < 2:
        return {}

    # Align lengths (truncate to shortest)
    min_len = min(len(strategy_returns_dict[n]) for n in names)
    if min_len < 2:
        return {}

    matrix = {}
    for n1 in names:
        r1 = strategy_returns_dict[n1][-min_len:]
        row = {}
        for n2 in names:
            r2 = strategy_returns_dict[n2][-min_len:]
            if n1 == n2:
                row[n2] = 1.0
            else:
                corr = np.corrcoef(r1, r2)[0, 1]
                row[n2] = float(corr) if not np.isnan(corr) else 0.0
        matrix[n1] = row
    return matrix


def correlation_from_equity(strategy_equity_dict):
    """
    Convenience: correlation matrix from {name: [{t,v},...]} equity curves.
    """
    returns_dict = {}
    for name, curve in strategy_equity_dict.items():
        dr = _daily_returns(curve)
        if len(dr) >= 2:
            returns_dict[name] = dr
    return correlation_matrix(returns_dict)


# ═══════════════════════════════════════════════════════════
# Composite risk summary
# ═══════════════════════════════════════════════════════════

def risk_summary(equity_history, strategy_equity=None):
    """
    One-shot: compute all risk metrics for a portfolio.

    Args:
        equity_history: portfolio-level equity curve [{t, v}, ...]
        strategy_equity: optional {name: [{t, v}, ...]}

    Returns dict with VaR, CVaR, MaxDD, Calmar, Sharpe, Sortino,
    and optionally correlation/cross-strategy metrics.
    """
    dr = _daily_returns(equity_history)
    dd = max_drawdown(equity_history)

    summary = {
        "var_95": round(var_95(dr), 6),
        "cvar_95": round(cvar_95(dr), 6),
        "max_drawdown": round(dd, 6),
        "calmar_ratio": round(calmar_ratio(dr, dd), 4),
        "sharpe": round(sharpe(dr), 4),
        "sortino": round(sortino(dr), 4),
        "num_observations": len(dr),
    }

    if strategy_equity and len(strategy_equity) >= 2:
        summary["correlation"] = correlation_from_equity(strategy_equity)

        # Per-strategy metrics
        per_strat = {}
        for name, curve in strategy_equity.items():
            sdr = _daily_returns(curve)
            sdd = max_drawdown(curve)
            per_strat[name] = {
                "sharpe": round(sharpe(sdr), 4),
                "sortino": round(sortino(sdr), 4),
                "max_drawdown": round(sdd, 6),
                "calmar_ratio": round(calmar_ratio(sdr, sdd), 4),
                "var_95": round(var_95(sdr), 6),
                "cvar_95": round(cvar_95(sdr), 6),
            }
        summary["per_strategy"] = per_strat

    return summary
