"""TDD for :mod:`cerbero_bite.core.kelly_recalibration`.

Spec: ``docs/03-algorithms.md §7``.
"""

from __future__ import annotations

from datetime import UTC, datetime, timedelta
from decimal import Decimal
from uuid import uuid4

import pytest

from cerbero_bite.config import StrategyConfig, golden_config
from cerbero_bite.core.kelly_recalibration import TradeRecord, recalibrate


@pytest.fixture
def cfg() -> StrategyConfig:
    return golden_config()


@pytest.fixture
def now() -> datetime:
    return datetime(2026, 4, 27, 14, 0, tzinfo=UTC)


def _trade(
    *,
    pnl_usd: str,
    risk_usd: str = "100",
    days_ago: int = 30,
    outcome: str = "CLOSE_PROFIT",
    now_dt: datetime = datetime(2026, 4, 27, 14, 0, tzinfo=UTC),
) -> TradeRecord:
    return TradeRecord(
        proposal_id=uuid4(),
        pnl_usd=Decimal(pnl_usd),
        risk_usd=Decimal(risk_usd),
        closed_at=now_dt - timedelta(days=days_ago),
        outcome=outcome,
    )


# ---------------------------------------------------------------------------
# Confidence bands and recommended fraction
# ---------------------------------------------------------------------------


def test_empty_history_returns_low_confidence_with_cfg_kelly(
    cfg: StrategyConfig, now: datetime
) -> None:
    res = recalibrate(trades=[], now=now, cfg=cfg)
    assert res.confidence == "low"
    assert res.sample_size == 0
    assert res.recommended_fraction == cfg.sizing.kelly_fraction
    assert res.win_rate == Decimal("0")
    assert res.full_kelly_pct == Decimal("0")
    assert res.quarter_kelly_pct == Decimal("0")


def test_low_sample_keeps_cfg_kelly_fraction(cfg: StrategyConfig, now: datetime) -> None:
    trades = [_trade(pnl_usd="10", now_dt=now) for _ in range(20)]
    trades += [_trade(pnl_usd="-15", now_dt=now) for _ in range(5)]
    res = recalibrate(trades=trades, now=now, cfg=cfg)
    assert res.sample_size == 25
    assert res.confidence == "low"
    assert res.recommended_fraction == cfg.sizing.kelly_fraction


def test_medium_sample_blends_quarter_kelly_with_cfg(
    cfg: StrategyConfig, now: datetime
) -> None:
    # 60 trades, 70% wins (avg_win=10/100=0.10), 30% losses (avg_loss=15/100=0.15)
    # b = 0.10 / 0.15 = 0.667
    # full_kelly = (0.7 × 0.667 - 0.3) / 0.667 = (0.467 - 0.3) / 0.667 = 0.25
    # quarter_kelly = 0.0625
    # blended = 0.5 × 0.0625 + 0.5 × 0.13 = 0.09625
    wins = [_trade(pnl_usd="10", now_dt=now) for _ in range(42)]
    losses = [_trade(pnl_usd="-15", now_dt=now) for _ in range(18)]
    res = recalibrate(trades=wins + losses, now=now, cfg=cfg)
    assert res.sample_size == 60
    assert res.confidence == "medium"
    assert res.win_rate == Decimal("0.7")
    expected = Decimal("0.5") * res.quarter_kelly_pct + Decimal("0.5") * cfg.sizing.kelly_fraction
    assert res.recommended_fraction == expected


def test_high_sample_adopts_quarter_kelly(cfg: StrategyConfig, now: datetime) -> None:
    wins = [_trade(pnl_usd="10", now_dt=now) for _ in range(120)]
    losses = [_trade(pnl_usd="-15", now_dt=now) for _ in range(40)]
    res = recalibrate(trades=wins + losses, now=now, cfg=cfg)
    assert res.sample_size == 160
    assert res.confidence == "high"
    assert res.recommended_fraction == res.quarter_kelly_pct


# ---------------------------------------------------------------------------
# Lookback filter
# ---------------------------------------------------------------------------


def test_trades_older_than_lookback_are_ignored(
    cfg: StrategyConfig, now: datetime
) -> None:
    fresh = [_trade(pnl_usd="10", days_ago=30, now_dt=now) for _ in range(40)]
    stale = [_trade(pnl_usd="-50", days_ago=400, now_dt=now) for _ in range(60)]
    res = recalibrate(trades=fresh + stale, now=now, cfg=cfg)
    # Only 40 trades in window; all wins → win_rate = 1.0
    assert res.sample_size == 40
    assert res.win_rate == Decimal("1")


# ---------------------------------------------------------------------------
# Edge cases for full_kelly arithmetic
# ---------------------------------------------------------------------------


def test_all_losses_clip_full_kelly_to_zero(cfg: StrategyConfig, now: datetime) -> None:
    trades = [_trade(pnl_usd="-20", now_dt=now) for _ in range(40)]
    res = recalibrate(trades=trades, now=now, cfg=cfg)
    assert res.win_rate == Decimal("0")
    assert res.full_kelly_pct == Decimal("0")
    assert res.quarter_kelly_pct == Decimal("0")


def test_all_wins_yield_full_kelly_equal_to_win_rate(
    cfg: StrategyConfig, now: datetime
) -> None:
    # No losses → division by zero. Convention: full_kelly = win_rate (= 1.0).
    trades = [_trade(pnl_usd="20", now_dt=now) for _ in range(40)]
    res = recalibrate(trades=trades, now=now, cfg=cfg)
    assert res.win_rate == Decimal("1")
    assert res.avg_loss_pct_risk == Decimal("0")
    assert res.full_kelly_pct == Decimal("1")
    assert res.quarter_kelly_pct == Decimal("0.25")


def test_avg_loss_uses_absolute_value(cfg: StrategyConfig, now: datetime) -> None:
    wins = [_trade(pnl_usd="10", now_dt=now) for _ in range(30)]
    losses = [_trade(pnl_usd="-20", now_dt=now) for _ in range(30)]
    res = recalibrate(trades=wins + losses, now=now, cfg=cfg)
    # avg_win_pct_risk = 10/100 = 0.10; avg_loss_pct_risk = 20/100 = 0.20 (positive)
    assert res.avg_win_pct_risk == Decimal("0.1")
    assert res.avg_loss_pct_risk == Decimal("0.2")


def test_zero_pnl_counts_as_loss_for_winrate(cfg: StrategyConfig, now: datetime) -> None:
    # Spec: "trade con pnl > 0" → wins. 0 is not a win.
    trades = [
        *[_trade(pnl_usd="10", now_dt=now) for _ in range(20)],
        *[_trade(pnl_usd="0", now_dt=now) for _ in range(20)],
    ]
    res = recalibrate(trades=trades, now=now, cfg=cfg)
    assert res.win_rate == Decimal("0.5")
