"""TDD per il backfill IV-RV (``scripts/backfill_iv_rv.py``).

Testa solo la parte pura (compute RV + assemblaggio record). I/O HTTP
e SQLite restano nel main del CLI: testati manualmente al deploy.
"""

from __future__ import annotations

import importlib.util
import sys
from datetime import UTC, date, datetime, timedelta
from decimal import Decimal
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parents[2]


def _load_backfill_module() -> object:
    """Load scripts/backfill_iv_rv.py as a module without polluting sys.path."""
    spec = importlib.util.spec_from_file_location(
        "_cerbero_bite_backfill_iv_rv", REPO_ROOT / "scripts" / "backfill_iv_rv.py"
    )
    if spec is None or spec.loader is None:
        raise RuntimeError("cannot load backfill_iv_rv module")
    module = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = module
    spec.loader.exec_module(module)
    return module


@pytest.fixture(scope="module")
def mod():
    return _load_backfill_module()


# ---------------------------------------------------------------------------
# compute_rv30d_annualized
# ---------------------------------------------------------------------------


def test_constant_prices_yield_zero_rv(mod) -> None:
    closes = [Decimal("100")] * 31  # 30 returns of log(1)=0
    rv = mod.compute_rv30d_annualized(closes)
    assert rv == Decimal("0")


def test_too_few_closes_raises(mod) -> None:
    with pytest.raises(ValueError, match="need at least 31 closes"):
        mod.compute_rv30d_annualized([Decimal("100")] * 10)


def test_monotonic_growth_yields_low_rv(mod) -> None:
    """Crescita +1% ogni giorno: log returns costanti → stdev = 0 → RV = 0."""
    closes = [Decimal("100") * (Decimal("1.01") ** i) for i in range(31)]
    rv = mod.compute_rv30d_annualized(closes)
    # Tutti i log returns sono identici (log 1.01) → stdev zero
    assert rv == Decimal("0")


def test_alternating_returns_yield_known_rv(mod) -> None:
    """Returns alternati ±2% ogni giorno: stdev nota."""
    # closes: 100, 102, 100, 102, ... (ricorda: returns = log(c[i]/c[i-1]))
    closes = [Decimal("100")] + [
        Decimal("102") if i % 2 == 0 else Decimal("100") for i in range(30)
    ]
    rv = mod.compute_rv30d_annualized(closes)
    # |log return| ~ 0.0198, stdev ≈ 0.0198 (alternano segno con media ≈ 0)
    # Annualized = 0.0198 * sqrt(365) * 100 ≈ 37.86 vol pts
    assert Decimal("36") <= rv <= Decimal("40")


# ---------------------------------------------------------------------------
# build_backfill_records
# ---------------------------------------------------------------------------


def test_build_records_skips_days_without_30d_history(mod) -> None:
    """Per i primi 30 giorni della serie spot, RV30d non è calcolabile."""
    today = date(2026, 5, 10)
    days = [today - timedelta(days=i) for i in range(45)]
    spots = {d.isoformat(): Decimal("100") for d in days}
    dvols = {d.isoformat(): Decimal("50") for d in days}

    records = mod.build_backfill_records(
        asset="ETH",
        spots_by_day=spots,
        dvols_by_day=dvols,
        oldest_day=today - timedelta(days=40),
    )
    # Per ogni record day, servono 30 giorni precedenti di spot.
    # Lo spot più vecchio è today-44; quindi il primo giorno computabile
    # è today-44+30 = today-14. Cap a oldest_day=today-40 → window day-14..day-0.
    assert len(records) == 15  # day-14..day-0 incluso
    for r in records:
        assert r.asset == "ETH"
        assert r.fetch_ok is True
        assert r.iv_minus_rv == Decimal("50")  # rv=0 con prezzi costanti
        assert r.timestamp.tzinfo == UTC
        assert r.timestamp.hour == 12


def test_build_records_filters_to_requested_window(mod) -> None:
    """oldest_day applicato come cutoff inferiore inclusivo."""
    today = date(2026, 5, 10)
    days = [today - timedelta(days=i) for i in range(45)]
    spots = {d.isoformat(): Decimal("100") for d in days}
    dvols = {d.isoformat(): Decimal("50") for d in days}

    records = mod.build_backfill_records(
        asset="BTC",
        spots_by_day=spots,
        dvols_by_day=dvols,
        oldest_day=today - timedelta(days=5),
    )
    # day-5..day-0 → 6 record
    assert len(records) == 6
    record_days = {r.timestamp.date() for r in records}
    assert record_days == {today - timedelta(days=i) for i in range(6)}


def test_build_records_skips_days_missing_dvol(mod) -> None:
    """Se manca DVOL per un giorno della finestra, lo si salta (no record)."""
    today = date(2026, 5, 10)
    days = [today - timedelta(days=i) for i in range(45)]
    spots = {d.isoformat(): Decimal("100") for d in days}
    dvols = {
        d.isoformat(): Decimal("50")
        for d in days
        if d != today - timedelta(days=2)
    }
    records = mod.build_backfill_records(
        asset="ETH",
        spots_by_day=spots,
        dvols_by_day=dvols,
        oldest_day=today - timedelta(days=5),
    )
    record_days = {r.timestamp.date() for r in records}
    assert today - timedelta(days=2) not in record_days
    assert len(records) == 5


def test_build_records_skips_days_missing_spot(mod) -> None:
    """Se manca lo spot del giorno target, no record per quel giorno."""
    today = date(2026, 5, 10)
    days = [today - timedelta(days=i) for i in range(45)]
    spots = {
        d.isoformat(): Decimal("100")
        for d in days
        if d != today - timedelta(days=2)
    }
    dvols = {d.isoformat(): Decimal("50") for d in days}
    records = mod.build_backfill_records(
        asset="ETH",
        spots_by_day=spots,
        dvols_by_day=dvols,
        oldest_day=today - timedelta(days=5),
    )
    record_days = {r.timestamp.date() for r in records}
    assert today - timedelta(days=2) not in record_days


def test_build_records_uses_noon_utc_timestamp(mod) -> None:
    today = date(2026, 5, 10)
    days = [today - timedelta(days=i) for i in range(35)]
    spots = {d.isoformat(): Decimal("100") for d in days}
    dvols = {d.isoformat(): Decimal("50") for d in days}
    records = mod.build_backfill_records(
        asset="ETH",
        spots_by_day=spots,
        dvols_by_day=dvols,
        oldest_day=today,
    )
    assert len(records) == 1
    assert records[0].timestamp == datetime(2026, 5, 10, 12, 0, tzinfo=UTC)
