"""Tests for phi accrual failure detector (Argus enhancement)."""

from __future__ import annotations

from opendeviationbar.phi_accrual import PhiAccrualDetector, _calc_phi


class TestPhiAccrualDetector:
    """Core detector behavior."""

    def test_no_heartbeats_returns_zero(self) -> None:
        d = PhiAccrualDetector()
        assert d.phi("BTCUSDT") == 0.0
        assert d.is_available("BTCUSDT")

    def test_regular_heartbeats_low_phi(self) -> None:
        d = PhiAccrualDetector(first_heartbeat_estimate_ms=1000)
        ts = 0.0
        for _ in range(20):
            d.heartbeat("BTCUSDT", ts)
            ts += 1000  # 1s intervals
        # Check phi 1.5s after last heartbeat (within normal range)
        phi = d.phi("BTCUSDT", ts + 500)
        assert phi < 3.0, f"phi={phi:.2f} should be low for 0.5s after 1s intervals"

    def test_long_silence_high_phi(self) -> None:
        d = PhiAccrualDetector(first_heartbeat_estimate_ms=1000)
        ts = 0.0
        for _ in range(20):
            d.heartbeat("BTCUSDT", ts)
            ts += 1000
        # Check phi 60s after last heartbeat (way outside normal)
        phi = d.phi("BTCUSDT", ts + 60_000)
        assert phi > 8.0, f"phi={phi:.2f} should be high for 60s silence after 1s intervals"

    def test_adapts_to_slow_symbols(self) -> None:
        """A slow symbol should have low phi within 1 stddev of mean."""
        import random
        rng = random.Random(42)
        d = PhiAccrualDetector(first_heartbeat_estimate_ms=300_000)
        ts = 0.0
        for _ in range(30):
            d.heartbeat("LOWVOL", ts)
            ts += 300_000 + rng.uniform(-30_000, 30_000)
        # 60s after last bar is well within the 5-min mean → low phi
        phi_short = d.phi("LOWVOL", ts + 60_000)
        assert phi_short < 3.0, f"phi={phi_short:.2f} should be low at +60s with 5min mean"
        # Contrast: a 1s-interval symbol at the same 60s gap should be high
        d2 = PhiAccrualDetector(first_heartbeat_estimate_ms=1000)
        ts2 = 0.0
        for _ in range(30):
            d2.heartbeat("FAST", ts2)
            ts2 += 1000 + rng.uniform(-100, 100)
        phi_fast = d2.phi("FAST", ts2 + 60_000)
        assert phi_fast > 8.0, f"phi={phi_fast:.2f} should be high for fast symbol at +60s"

    def test_per_symbol_independence(self) -> None:
        d = PhiAccrualDetector(first_heartbeat_estimate_ms=1000)
        ts = 0.0
        # BTCUSDT has frequent heartbeats
        for _ in range(20):
            d.heartbeat("BTCUSDT", ts)
            ts += 1000
        # ETHUSDT has infrequent heartbeats
        ts2 = 0.0
        for _ in range(20):
            d.heartbeat("ETHUSDT", ts2)
            ts2 += 10_000  # 10s intervals
        # After 5s silence: BTCUSDT should be suspicious, ETHUSDT should be fine
        phi_btc = d.phi("BTCUSDT", ts + 5000)
        phi_eth = d.phi("ETHUSDT", ts2 + 5000)
        assert phi_btc > phi_eth

    def test_reset_clears_history(self) -> None:
        d = PhiAccrualDetector()
        d.heartbeat("X", 0)
        d.heartbeat("X", 1000)
        assert d.phi("X", 100_000) > 0
        d.reset("X")
        assert d.phi("X") == 0.0

    def test_is_available_uses_threshold(self) -> None:
        d = PhiAccrualDetector(threshold=8.0, first_heartbeat_estimate_ms=1000)
        ts = 0.0
        for _ in range(20):
            d.heartbeat("A", ts)
            ts += 1000
        assert d.is_available("A", ts + 500)
        assert not d.is_available("A", ts + 60_000)


class TestCalcPhi:
    """Low-level phi calculation."""

    def test_at_mean_phi_near_zero(self) -> None:
        phi = _calc_phi(1000, 1000, 100)
        assert phi < 0.5

    def test_well_above_mean_high_phi(self) -> None:
        phi = _calc_phi(5000, 1000, 100)
        assert phi > 8.0

    def test_zero_std_dev_below_mean(self) -> None:
        assert _calc_phi(500, 1000, 0) == 0.0

    def test_zero_std_dev_above_mean(self) -> None:
        assert _calc_phi(1500, 1000, 0) == 16.0

    def test_phi_capped_at_16(self) -> None:
        phi = _calc_phi(1_000_000, 1000, 100)
        assert phi <= 16.0

    def test_phi_non_negative(self) -> None:
        phi = _calc_phi(0, 1000, 500)
        assert phi >= 0.0
