"""
Sharpe ratio from the paper portfolio's daily PnL curve, with a minimum-sample gate.

The input series is the closing total_pnl of each observed UTC day
(Database.get_daily_pnl_closes). Daily returns are PnL deltas normalized by
the paper bankroll:

    r_t = (pnl_t − pnl_{t−1}) / bankroll

Sharpe = mean(r) / sample_std(r) × √365, annualized — prediction markets
resolve every calendar day, so 365 is used instead of 252 trading days.
Risk-free rate is taken as 0.

Gate: with a tiny sample (e.g. 1 resolved trade over a flat curve plus one
+299 jump) any Sharpe value is statistically meaningless — artificially huge
or tiny depending on where the jump lands. So no numeric Sharpe is exposed
until BOTH minimums are met:

    days observed     >= MIN_DAYS_OBSERVED   (30)
    resolved trades   >= MIN_RESOLVED_TRADES (10)

Below either minimum the value is None with status "insufficient_sample".
A perfectly flat curve (zero variance) also yields None ("zero_variance"):
Sharpe is undefined there, not infinite.
"""
from statistics import mean, stdev
from typing import Optional

MIN_DAYS_OBSERVED = 30
MIN_RESOLVED_TRADES = 10
ANNUALIZATION_DAYS = 365

SHARPE_OK = "ok"
SHARPE_INSUFFICIENT = "insufficient_sample"
SHARPE_ZERO_VARIANCE = "zero_variance"


def daily_returns(daily_pnl_closes: list[float], bankroll: float) -> list[float]:
    """Bankroll-normalized day-over-day returns from a daily PnL-close series."""
    return [
        (curr - prev) / bankroll
        for prev, curr in zip(daily_pnl_closes, daily_pnl_closes[1:])
    ]


def compute_sharpe(daily_pnl_closes: list[float], bankroll: float) -> Optional[float]:
    """Annualized Sharpe of the daily PnL curve, or None if undefined.

    None when there are fewer than 2 returns (need 3+ daily closes) or the
    return series has zero variance. No sample-size gate here — see
    sharpe_with_gate() for the exposed value.
    """
    returns = daily_returns(daily_pnl_closes, bankroll)
    if len(returns) < 2:
        return None
    sd = stdev(returns)
    if sd == 0:
        return None
    return mean(returns) / sd * ANNUALIZATION_DAYS ** 0.5


def sharpe_with_gate(
    daily_pnl_closes: list[float],
    bankroll: float,
    resolved_count: int,
) -> tuple[Optional[float], str]:
    """Return (sharpe, status) applying the minimum-sample gate.

    status: "ok"                  — sharpe is a meaningful float
            "insufficient_sample" — sample below minimums, sharpe is None
            "zero_variance"       — sample OK but flat curve, sharpe is None
    """
    days_observed = len(daily_pnl_closes)
    if days_observed < MIN_DAYS_OBSERVED or resolved_count < MIN_RESOLVED_TRADES:
        return None, SHARPE_INSUFFICIENT
    sharpe = compute_sharpe(daily_pnl_closes, bankroll)
    if sharpe is None:
        return None, SHARPE_ZERO_VARIANCE
    return sharpe, SHARPE_OK
