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

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

La funzione è una pura statistica: riceve già la finestra di valori scelta
dal caller e il numero di giorni distinti coperti dalla storia disponibile
(``n_days``), e restituisce ``max(percentile, floor)`` o ``None`` durante
il warmup hard. La selezione della finestra (target_days vs min_days vs
intera storia) è responsabilità del caller (repository + entry_cycle).
"""

from __future__ import annotations

from decimal import Decimal

import pytest

from cerbero_bite.core.adaptive_threshold import compute_adaptive_threshold

# ---------------------------------------------------------------------------
# Warmup hard: nessun giorno disponibile
# ---------------------------------------------------------------------------


def test_n_days_zero_returns_none() -> None:
    """Storia vuota o nessun giorno coperto → warmup hard."""
    out = compute_adaptive_threshold(
        history=[],
        n_days=0,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
    )
    assert out is None


def test_n_days_zero_with_values_still_returns_none() -> None:
    """Difensivo: se il caller passa n_days=0 ma valori non vuoti, warmup
    hard vince comunque (gate disabilitato)."""
    out = compute_adaptive_threshold(
        history=[Decimal("3")] * 10,
        n_days=0,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
    )
    assert out is None


def test_empty_history_with_positive_n_days_returns_none() -> None:
    """Difensivo: history vuota anche con n_days>0 → None."""
    out = compute_adaptive_threshold(
        history=[],
        n_days=5,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
    )
    assert out is None


# ---------------------------------------------------------------------------
# Calcolo percentile sulla finestra ricevuta
# ---------------------------------------------------------------------------


def test_n_days_one_returns_percentile_of_history() -> None:
    """Singolo giorno con tick a 15 min (96 valori): P25 standard."""
    history = [Decimal(i) / Decimal("10") for i in range(96)]  # 0.0..9.5
    out = compute_adaptive_threshold(
        history=history,
        n_days=1,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
    )
    # 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 test_window_chosen_by_caller_is_used_verbatim() -> None:
    """La funzione NON fa slicing: usa esattamente la history ricevuta."""
    history = [Decimal(i) for i in range(1, 201)]  # 1..200
    out = compute_adaptive_threshold(
        history=history,
        n_days=30,
        percentile=Decimal("0.5"),
        absolute_floor=Decimal("0"),
    )
    # P50 di [1..200] = (200+1)/2 = 100.5
    assert out is not None
    assert Decimal("100") <= out <= Decimal("101")


def test_mixed_cadence_window_no_special_treatment() -> None:
    """Mix di valori (es. backfill daily + tick live) trattato come una
    distribuzione qualunque: il caller ha già scelto la finestra; la
    funzione calcola il percentile sui valori ricevuti uno-a-uno."""
    # 30 valori "daily backfill" (uno per giorno) + 96 tick "live"
    history = [Decimal("5")] * 30 + [Decimal("8")] * 96
    out = compute_adaptive_threshold(
        history=history,
        n_days=31,
        percentile=Decimal("0.25"),
        absolute_floor=Decimal("0"),
    )
    # Sorted: 30 ×5, 96 ×8. P25 a indice 0.25*125 = 31.25 → tra 5 e 8.
    # NumPy linear: sorted_v[31]=8, sorted_v[32]=8 → 8.
    # Verifica solo l'estremo superiore della famiglia di valori sorted.
    assert out is not None
    assert out in (Decimal("5"), Decimal("8"))


# ---------------------------------------------------------------------------
# Floor binding
# ---------------------------------------------------------------------------


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


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


# ---------------------------------------------------------------------------
# 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,
            n_days=10,
            percentile=Decimal("1.5"),
            absolute_floor=Decimal("0"),
        )


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


def test_invalid_negative_n_days_raises() -> None:
    with pytest.raises(ValueError, match="n_days must be >= 0"):
        compute_adaptive_threshold(
            history=[Decimal("1")] * 10,
            n_days=-1,
            percentile=Decimal("0.25"),
            absolute_floor=Decimal("0"),
        )
