"""TDD per :mod:`cerbero_bite.core.adaptive_threshold`.

Spec: ``docs/superpowers/specs/2026-05-08-iv-rv-adaptive-gate-design.md``.
"""

from __future__ import annotations

from decimal import Decimal

import pytest

from cerbero_bite.core.adaptive_threshold import compute_adaptive_threshold


# ---------------------------------------------------------------------------
# Warmup
# ---------------------------------------------------------------------------


def test_empty_history_returns_none() -> None:
    out = compute_adaptive_threshold(
        history=[],
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
        min_days=30,
        target_days=60,
    )
    assert out is None


def test_history_under_one_day_returns_none() -> None:
    out = compute_adaptive_threshold(
        history=[Decimal("3")] * 50,  # 50 tick < 96
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
        min_days=30,
        target_days=60,
    )
    assert out is None


def test_history_exactly_one_day_returns_percentile() -> None:
    history = [Decimal(i) / Decimal("10") for i in range(96)]  # 0.0..9.5
    out = compute_adaptive_threshold(
        history=history,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
        min_days=30,
        target_days=60,
    )
    # P25 di [0.0..9.5] passo 0.1 con method='linear': k=23.75, val ≈ 2.375
    assert out is not None
    assert Decimal("2.3") < out < Decimal("2.5")


def _ramp(n: int, base: Decimal = Decimal("1")) -> list[Decimal]:
    """Ramp lineare 1, 2, 3, ... per testare in modo predicibile il P25."""
    return [base * Decimal(i + 1) for i in range(n)]


def test_below_min_days_uses_full_history() -> None:
    # 5 giorni di storia (5*96=480 tick), min_days=30, target=60.
    # Window = full history.
    history = _ramp(480)
    out = compute_adaptive_threshold(
        history=history,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
        min_days=30,
        target_days=60,
    )
    # P25 di ramp [1..480] = ~120.75 (k=479*0.25=119.75, ramp[119]=120, ramp[120]=121)
    assert out is not None
    assert Decimal("120") <= out <= Decimal("121")


def test_between_min_and_target_uses_min_window() -> None:
    # 50 giorni di storia (4800 tick), min_days=30, target=60. Window = ultimi 30g.
    history = _ramp(4800)  # values 1..4800
    out = compute_adaptive_threshold(
        history=history,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
        min_days=30,
        target_days=60,
    )
    # Window = ultimi 30*96=2880, valori 1921..4800, P25 ≈ 2640
    assert out is not None
    assert Decimal("2630") <= out <= Decimal("2650")


def test_above_target_uses_target_window() -> None:
    # 100 giorni (9600 tick), target=60. Window = ultimi 60g.
    history = _ramp(9600)  # values 1..9600
    out = compute_adaptive_threshold(
        history=history,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
        min_days=30,
        target_days=60,
    )
    # Window = ultimi 5760, valori 3841..9600, P25 ≈ 5280
    assert out is not None
    assert Decimal("5270") <= out <= Decimal("5290")


def test_floor_binding_overrides_low_percentile() -> None:
    history = [Decimal("0.5")] * 200
    out = compute_adaptive_threshold(
        history=history,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("3"),
        min_days=30,
        target_days=60,
    )
    assert out == Decimal("3")


def test_floor_not_binding_returns_percentile() -> None:
    history = [Decimal("5")] * 200
    out = compute_adaptive_threshold(
        history=history,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
        min_days=30,
        target_days=60,
    )
    assert out == Decimal("5")


def test_median_percentile_returns_p50() -> None:
    history = _ramp(200)
    out = compute_adaptive_threshold(
        history=history,
        percentile=Decimal("0.5"),
        absolute_floor=Decimal("0"),
        min_days=30,
        target_days=60,
    )
    # P50 di [1..200] = (200+1)/2 = 100.5
    assert out is not None
    assert Decimal("100") <= out <= Decimal("101")


def test_exactly_min_days_uses_min_window() -> None:
    """Boundary: history == min_days*96 → window is min_days (per spec 9.1 item 9)."""
    history = _ramp(30 * 96)  # exactly 2880 ticks
    out = compute_adaptive_threshold(
        history=history,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
        min_days=30,
        target_days=60,
    )
    # Window = last 2880 = all history; P25 of ramp [1..2880] ≈ 720.75
    assert out is not None
    assert Decimal("720") <= out <= Decimal("721")


def test_exactly_target_days_uses_target_window() -> None:
    """Boundary: history == target_days*96 → window is target_days."""
    history = _ramp(60 * 96)  # exactly 5760 ticks
    out = compute_adaptive_threshold(
        history=history,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
        min_days=30,
        target_days=60,
    )
    # Window = last 5760 = all history; P25 of ramp [1..5760] ≈ 1440.75
    assert out is not None
    assert Decimal("1440") <= out <= Decimal("1441")


# ---------------------------------------------------------------------------
# Input validation
# ---------------------------------------------------------------------------


def test_invalid_percentile_above_one_raises() -> None:
    with pytest.raises(ValueError, match="percentile must be in"):
        compute_adaptive_threshold(
            history=[Decimal("1")] * 200,
            percentile=Decimal("1.5"),
            absolute_floor=Decimal("0"),
            min_days=30,
            target_days=60,
        )


def test_invalid_percentile_negative_raises() -> None:
    with pytest.raises(ValueError, match="percentile must be in"):
        compute_adaptive_threshold(
            history=[Decimal("1")] * 200,
            percentile=Decimal("-0.1"),
            absolute_floor=Decimal("0"),
            min_days=30,
            target_days=60,
        )


def test_invalid_window_inverted_raises() -> None:
    with pytest.raises(ValueError, match="min_days < target_days"):
        compute_adaptive_threshold(
            history=[Decimal("1")] * 200,
            percentile=Decimal("0.25"),
            absolute_floor=Decimal("0"),
            min_days=60,
            target_days=30,
        )


def test_invalid_window_zero_raises() -> None:
    with pytest.raises(ValueError, match="min_days < target_days"):
        compute_adaptive_threshold(
            history=[Decimal("1")] * 200,
            percentile=Decimal("0.25"),
            absolute_floor=Decimal("0"),
            min_days=0,
            target_days=60,
        )
