"""Falsification gates + risk-parity allocation on the honest per-sleeve return series. Pure functions.

A "series" is {epoch_day: daily_return}. risk_parity_weights sizes sleeves by inverse honest vol (adaptive,
NOT an in-sample Sharpe grid); book_returns aggregates the weighted sleeves over their COMMON days."""
from __future__ import annotations

import statistics as st

from fxhnt.application.nav_stats import nav_backfill_stats


def risk_parity_weights(returns_by_sleeve: dict[str, dict[int, float]]) -> dict[str, float]:
    """Weight each sleeve ∝ 1/vol (population stdev of its daily returns), normalized to sum to 1. A sleeve
    with zero/degenerate vol gets weight 0; if ALL are degenerate, fall back to equal weight."""
    inv = {}
    for s, series in returns_by_sleeve.items():
        vals = list(series.values())
        vol = st.pstdev(vals) if len(vals) > 1 else 0.0
        inv[s] = (1.0 / vol) if vol > 0.0 else 0.0
    total = sum(inv.values())
    if total <= 0.0:
        n = len(returns_by_sleeve)
        return {s: 1.0 / n for s in returns_by_sleeve} if n else {}
    return {s: v / total for s, v in inv.items()}


def book_returns(returns_by_sleeve: dict[str, dict[int, float]],
                 weights: dict[str, float]) -> dict[int, float]:
    """Weighted book return over the UNION of all sleeves' days; a sleeve contributes 0 on days it is not
    active (event-driven sleeves like unlock are sparse — intersecting would delete the other sleeves' real
    returns). {day: Σ_sleeve w_s · r_s,d (0 where sleeve inactive)}."""
    days: set[int] = set()
    for series in returns_by_sleeve.values():
        days |= set(series)
    return {d: sum(weights.get(s, 0.0) * returns_by_sleeve[s].get(d, 0.0) for s in returns_by_sleeve)
            for d in sorted(days)}


def _nav(series: dict[int, float]) -> list[float]:
    """Compounded equity path [1.0, ...] from a {day: daily_return} series (sorted by day)."""
    nav, v = [], 1.0
    for d in sorted(series):
        v *= (1.0 + series[d])
        nav.append(v)
    return [1.0] + nav


def sharpe_of(series: dict[int, float]) -> float:
    """Annualised Sharpe of a daily-return series (via nav_backfill_stats on its compounded nav)."""
    return nav_backfill_stats(_nav(series)).sharpe


def gate_survives(series: dict[int, float], *, min_sharpe: float = 1.0) -> bool:
    """G1: the honest edge clears a minimum Sharpe."""
    return sharpe_of(series) > min_sharpe


def loo_marginal(returns_by_sleeve: dict[str, dict[int, float]]) -> dict[str, float]:
    """G2: leave-one-out marginal Sharpe — full risk-parity book Sharpe minus the drop-one book Sharpe,
    per sleeve. A sleeve whose marginal is ~0 or negative is dead weight."""
    full = sharpe_of(book_returns(returns_by_sleeve, risk_parity_weights(returns_by_sleeve)))
    out = {}
    for drop in returns_by_sleeve:
        rest = {s: v for s, v in returns_by_sleeve.items() if s != drop}
        rest_sh = sharpe_of(book_returns(rest, risk_parity_weights(rest))) if rest else 0.0
        out[drop] = full - rest_sh
    return out


def beats_concentration(returns_by_sleeve: dict[str, dict[int, float]]) -> bool:
    """G3: the risk-parity book beats the best single sleeve (diversification is real, not a label)."""
    book_sh = sharpe_of(book_returns(returns_by_sleeve, risk_parity_weights(returns_by_sleeve)))
    best_single = max((sharpe_of(v) for v in returns_by_sleeve.values()), default=0.0)
    return book_sh > best_single


def ab_beats_nothing(base_series: dict[int, float], derisked_series: dict[int, float]) -> bool:
    """G4: a proposed de-risk rule only 'wins' if its honest Sharpe strictly exceeds doing nothing. Keeps
    reactive de-sizing (vol-target / drawdown-trigger — A/B-rejected 3x) out unless it genuinely earns its
    place. Identical series (no change) does NOT beat nothing."""
    return sharpe_of(derisked_series) > sharpe_of(base_series)
