"""worst_basis pure computation — the intraday-liquidation tail the daily-close carry accounting misses.

`worst_basis(day)` = min over the day's aligned minutes of the cumulative Σ(spot_1m_ret − perp_1m_ret)
since the day's first aligned minute. The LUNA-pattern fixture proves a within-day excursion that recovers
by close yields a worst_basis MUCH worse than the close-basis."""
from fxhnt.application.worst_basis import worst_basis_by_day

_DAY_MS = 86_400_000
_MIN_MS = 60_000


def _series(start_ms: int, closes: list[float]) -> dict[int, float]:
    """{epoch_ms: close} over consecutive 1m bars starting at start_ms."""
    return {start_ms + i * _MIN_MS: c for i, c in enumerate(closes)}


def test_luna_pattern_intraday_excursion_recovering_to_flat_close():
    # One day. Perp CRASHES intraday (the short leg moves violently against a delta-neutral carry) then
    # recovers to ~flat by close; spot is steady. cumret(spot - perp): when perp drops, perp_ret<0 so
    # (spot_ret - perp_ret) spikes POSITIVE for the long-spot/short-perp delta-neutral... but worst_basis
    # is the MINIMUM cumulative basis. Construct a perp that SPIKES UP intraday (squeeze) so the short-perp
    # leg loses: perp_ret>0 -> (spot_ret - perp_ret)<0 -> cumulative basis goes deeply negative mid-day,
    # then perp falls back to open by close so the close-basis ~0.
    day0 = 1_652_313_600_000  # 2022-05-12T00:00:00Z, an epoch-ms day boundary
    # spot flat at 100 the whole day
    spot = _series(day0, [100.0, 100.0, 100.0, 100.0, 100.0])
    # perp: 100 -> 200 (squeeze, short loses ~ -100% basis) -> back to 100 by close (recovers)
    perp = _series(day0, [100.0, 200.0, 150.0, 120.0, 100.0])
    out = worst_basis_by_day(perp_close=perp, spot_close=spot)
    epoch_day = day0 // _DAY_MS
    assert epoch_day in out
    wb = out[epoch_day]
    # Mid-day cumulative basis at the spike: spot_ret=0, perp_ret=+1.0 -> step -1.0 -> cum -1.0 (worst).
    assert abs(wb - (-1.0)) < 1e-9
    # Close basis = cumulative to the LAST minute = (spot 100/100 -1) - (perp 100/100 -1) = 0 -> ~0.
    # worst_basis is far worse than close-basis (the whole point).
    close_basis = (spot[day0 + 4 * _MIN_MS] / spot[day0] - 1.0) - (perp[day0 + 4 * _MIN_MS] / perp[day0] - 1.0)
    assert abs(close_basis) < 1e-9
    assert wb < close_basis - 0.5


def test_worst_basis_is_negative_or_zero_and_bounded_by_first_minute():
    # Cumulative basis starts at 0 at the first aligned minute, so worst_basis <= 0 always.
    day0 = 1_652_313_600_000
    spot = _series(day0, [100.0, 101.0, 102.0])   # spot rises
    perp = _series(day0, [100.0, 100.0, 100.0])   # perp flat -> basis only goes POSITIVE
    out = worst_basis_by_day(perp_close=perp, spot_close=spot)
    epoch_day = day0 // _DAY_MS
    assert out[epoch_day] == 0.0   # never dips below the first-minute zero


def test_only_common_minutes_are_aligned():
    # perp has an extra minute spot lacks; alignment uses the intersection only.
    day0 = 1_652_313_600_000
    spot = {day0: 100.0, day0 + 2 * _MIN_MS: 100.0}            # minutes 0 and 2
    perp = {day0: 100.0, day0 + 1 * _MIN_MS: 200.0, day0 + 2 * _MIN_MS: 100.0}  # 0,1,2
    out = worst_basis_by_day(perp_close=perp, spot_close=spot)
    epoch_day = day0 // _DAY_MS
    # Aligned minutes: {0, 2}. Step at minute 2: spot_ret 0, perp_ret 0 -> 0. The spike at minute 1 is
    # DROPPED (spot lacks it). So worst_basis = 0 (the first-minute zero), NOT -1.0.
    assert out[epoch_day] == 0.0


def test_single_aligned_bar_in_a_day_no_entry():
    # A day with only one aligned minute has no return step -> no worst_basis entry.
    day0 = 1_652_313_600_000
    spot = {day0: 100.0}
    perp = {day0: 100.0}
    assert worst_basis_by_day(perp_close=perp, spot_close=spot) == {}


def test_empty_inputs_no_entry():
    assert worst_basis_by_day(perp_close={}, spot_close={}) == {}
    assert worst_basis_by_day(perp_close={1: 1.0}, spot_close={}) == {}


def test_multiple_days_keyed_independently():
    day0 = 1_652_313_600_000
    day1 = day0 + _DAY_MS
    # day0: a -50% basis excursion; day1: a -100% excursion
    spot = {**_series(day0, [100.0, 100.0, 100.0]), **_series(day1, [100.0, 100.0, 100.0])}
    perp = {**_series(day0, [100.0, 150.0, 100.0]), **_series(day1, [100.0, 200.0, 100.0])}
    out = worst_basis_by_day(perp_close=perp, spot_close=spot)
    assert abs(out[day0 // _DAY_MS] - (-0.5)) < 1e-9
    assert abs(out[day1 // _DAY_MS] - (-1.0)) < 1e-9


def test_nonpositive_close_skips_step_no_crash():
    # A zero/negative close (bad bar) can't form a return; the step is skipped, no division error.
    day0 = 1_652_313_600_000
    spot = _series(day0, [100.0, 0.0, 100.0])
    perp = _series(day0, [100.0, 100.0, 100.0])
    out = worst_basis_by_day(perp_close=perp, spot_close=spot)
    # Steps that involve a non-positive price are skipped; remaining steps are well-defined -> no crash.
    assert (day0 // _DAY_MS) in out
