"""Monthly Kelly recalibration (``docs/03-algorithms.md §7``).

Pure function over a list of closed-trade records. Returns a fresh
:class:`KellyResult` summarising the empirical edge and a recommendation
that is *never* applied automatically — the orchestrator surfaces it as
a monthly report and Adriano decides whether to bump
``sizing.kelly_fraction`` in ``strategy.yaml``.
"""

from __future__ import annotations

from datetime import datetime, timedelta
from decimal import Decimal
from typing import Literal
from uuid import UUID

from pydantic import BaseModel, ConfigDict

from cerbero_bite.config import StrategyConfig

__all__ = ["Confidence", "KellyResult", "TradeRecord", "recalibrate"]


Confidence = Literal["low", "medium", "high"]


class TradeRecord(BaseModel):
    """One closed trade fed to the recalibration."""

    model_config = ConfigDict(frozen=True, extra="forbid")

    proposal_id: UUID
    pnl_usd: Decimal
    risk_usd: Decimal
    closed_at: datetime
    outcome: str


class KellyResult(BaseModel):
    """Recommendation report; the operator may or may not apply it."""

    model_config = ConfigDict(frozen=True, extra="forbid")

    win_rate: Decimal
    avg_win_pct_risk: Decimal
    avg_loss_pct_risk: Decimal
    full_kelly_pct: Decimal
    quarter_kelly_pct: Decimal
    sample_size: int
    recommended_fraction: Decimal
    confidence: Confidence


def _within_lookback(
    trades: list[TradeRecord],
    *,
    now: datetime,
    lookback_days: int,
) -> list[TradeRecord]:
    cutoff = now - timedelta(days=lookback_days)
    return [t for t in trades if t.closed_at >= cutoff]


def _confidence_for(
    n: int,
    *,
    low_threshold: int,
    high_threshold: int,
) -> Confidence:
    if n < low_threshold:
        return "low"
    if n < high_threshold:
        return "medium"
    return "high"


def recalibrate(
    *,
    trades: list[TradeRecord],
    now: datetime,
    cfg: StrategyConfig,
) -> KellyResult:
    """Return the empirical Kelly summary + recommended fraction."""
    krc = cfg.kelly_recalibration
    in_window = _within_lookback(trades, now=now, lookback_days=krc.lookback_days)
    n = len(in_window)

    if n == 0:
        return KellyResult(
            win_rate=Decimal("0"),
            avg_win_pct_risk=Decimal("0"),
            avg_loss_pct_risk=Decimal("0"),
            full_kelly_pct=Decimal("0"),
            quarter_kelly_pct=Decimal("0"),
            sample_size=0,
            recommended_fraction=cfg.sizing.kelly_fraction,
            confidence="low",
        )

    wins = [t for t in in_window if t.pnl_usd > 0]
    losses = [t for t in in_window if t.pnl_usd < 0]
    win_rate = Decimal(len(wins)) / Decimal(n)

    avg_win = (
        sum((t.pnl_usd / t.risk_usd for t in wins), Decimal("0")) / Decimal(len(wins))
        if wins
        else Decimal("0")
    )
    avg_loss = (
        sum((-t.pnl_usd / t.risk_usd for t in losses), Decimal("0")) / Decimal(len(losses))
        if losses
        else Decimal("0")
    )

    if avg_loss == 0:
        # No losses — fall back to win_rate as an upper bound on full Kelly.
        full_kelly = win_rate
    else:
        b = avg_win / avg_loss
        if b == 0:
            full_kelly = Decimal("0")
        else:
            full_kelly = (win_rate * b - (Decimal("1") - win_rate)) / b
            if full_kelly < 0:
                full_kelly = Decimal("0")

    quarter_kelly = full_kelly * Decimal("0.25")

    confidence = _confidence_for(
        n,
        low_threshold=krc.min_sample_low_confidence,
        high_threshold=krc.min_sample_high_confidence,
    )

    if confidence == "low":
        recommended = cfg.sizing.kelly_fraction
    elif confidence == "medium":
        weight = krc.weight_when_medium_confidence
        recommended = weight * quarter_kelly + (Decimal("1") - weight) * cfg.sizing.kelly_fraction
    else:
        recommended = quarter_kelly

    return KellyResult(
        win_rate=win_rate,
        avg_win_pct_risk=avg_win,
        avg_loss_pct_risk=avg_loss,
        full_kelly_pct=full_kelly,
        quarter_kelly_pct=quarter_kelly,
        sample_size=n,
        recommended_fraction=recommended,
        confidence=confidence,
    )
