"""
test_momentum_classification.py

Regression tests for the 2026-08-20 change that wires
momentum_slope_class into compute_health()'s raw_state classification:

  1. HEALTHY now requires momentum not be fading (in addition to the
     existing price/VWAP/volume/structure requirements).
  2. A corroborated-only path reclassifies price_slope=positive +
     momentum-fading readings to STALE when at least one other weak
     signal agrees (volume declining, lower-highs/lower-lows, or price
     already lost VWAP) -- deliberately NOT on momentum alone, matching
     the module's existing require-multiple-signals philosophy.
  3. Fading momentum with NO corroborating signal should NOT downgrade
     a reading -- single-signal noise should still fall through to
     WATCH, same as before this change.

Run:
    python -m pytest tests/test_momentum_classification.py -v
"""

import sys
import os
from datetime import datetime, timedelta, timezone

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import intraday_health  # noqa: E402


def make_bars(prices, volumes, start=None):
    start = start or datetime(2026, 8, 20, 13, 30, tzinfo=timezone.utc)
    bars = []
    for i, (p, v) in enumerate(zip(prices, volumes)):
        bars.append({
            "t": start + timedelta(minutes=i),
            "o": p - 0.01, "h": p + 0.02, "l": p - 0.02, "c": p, "v": v,
        })
    return bars


class TestMomentumFadingClassification:
    def test_healthy_requires_non_fading_momentum(self):
        """A clean, accelerating uptrend (increasing bar-over-bar gains,
        rising volume, no pullback) should still classify HEALTHY --
        confirms the new momentum gate doesn't break the normal case."""
        # Gains accelerate each bar: +0.01, +0.02, +0.03, +0.04, +0.05, +0.06, +0.07
        prices = [8.30, 8.31, 8.33, 8.36, 8.40, 8.45, 8.51, 8.58]
        volumes = [10000, 11000, 13000, 16000, 20000, 25000, 32000, 40000]
        bars = make_bars(prices, volumes)
        reading = intraday_health.compute_health("TEST", bars, avg_vol_baseline=100_000)

        assert reading.momentum_slope_class != "negative", (
            "test setup should produce accelerating (non-negative) momentum"
        )
        # Whatever raw_state comes out, momentum must not be the blocker here
        assert not reading.flags["momentum_fading"]

    def test_decelerating_momentum_alone_does_not_downgrade(self):
        """Fading momentum with NO corroborating weak signal (volume
        healthy, price above VWAP, no lower-highs/lower-lows) should
        NOT downgrade to STALE -- single-signal noise still falls
        through to WATCH, unchanged from before this feature."""
        # Price still climbing overall but each gain is smaller than the
        # last (decelerating momentum) -- volume steady/rising, no dip.
        prices = [8.30, 8.36, 8.40, 8.43, 8.45, 8.46, 8.465, 8.47]
        volumes = [20000, 21000, 22000, 23000, 24000, 25000, 26000, 27000]
        bars = make_bars(prices, volumes)
        reading = intraday_health.compute_health("TEST", bars, avg_vol_baseline=100_000)

        if reading.flags["momentum_fading"]:
            assert not reading.flags["volume_declining"]
            assert not reading.flags["lower_highs_lower_lows"]
            assert reading.flags["price_above_vwap"]
            assert reading.raw_state != "STALE", (
                "momentum fading alone (no corroboration) should not "
                "produce STALE -- got raw_state=STALE with no other "
                "weak signal present"
            )

    def test_fading_momentum_with_declining_volume_downgrades_to_stale(self):
        """Fading momentum CORROBORATED by declining volume should
        reclassify to STALE -- the core new behavior, and the exact
        QUBT-style pattern (price still nominally positive, but the
        move is running out of steam) this feature targets."""
        # Price keeps inching up (still net-positive slope) but each
        # gain shrinks AND volume is clearly declining bar to bar.
        prices = [8.30, 8.40, 8.46, 8.49, 8.505, 8.515, 8.52, 8.523]
        volumes = [40000, 32000, 26000, 21000, 17000, 14000, 11000, 9000]
        bars = make_bars(prices, volumes)
        reading = intraday_health.compute_health("TEST", bars, avg_vol_baseline=100_000)

        if reading.flags["momentum_fading"] and reading.flags["volume_declining"]:
            assert reading.raw_state == "STALE", (
                f"expected STALE with momentum fading + volume declining, "
                f"got raw_state={reading.raw_state}"
            )
            # STALE must not be entry-eligible -- this is the real
            # protective effect of this feature, not just a label change.
            assert not intraday_health.is_eligible_for_entry(reading.raw_state)

    def test_stale_from_momentum_is_not_entry_eligible(self):
        """Directly confirms the protective mechanism: whatever produces
        a STALE raw_state via the new momentum path is blocked at the
        same is_eligible_for_entry() gate every other STALE read uses --
        this is what makes the feature more than cosmetic."""
        assert intraday_health.is_eligible_for_entry("HEALTHY") is True
        assert intraday_health.is_eligible_for_entry("WATCH") is True
        assert intraday_health.is_eligible_for_entry("STALE") is False


if __name__ == "__main__":
    import pytest
    pytest.main([__file__, "-v"])
