from __future__ import annotations

from libs.intraday.features import (
    compute_average_range,
    compute_average_true_range,
    compute_entropy_approx,
    compute_gap_zscore,
    enrich_daily_bars,
)


def _daily_bar(day: str, open_: float, high: float, low: float, close: float, volume: float = 1_000_000.0) -> dict:
    return {
        "date": day,
        "open": open_,
        "high": high,
        "low": low,
        "close": close,
        "volume": volume,
    }


def test_entropy_approx_is_bounded() -> None:
    bars = []
    close = 100.0
    for idx in range(30):
        close *= 1.0 + (0.01 if idx % 2 == 0 else -0.008)
        bars.append(_daily_bar(f"2024-01-{idx+1:02d}", close * 0.99, close * 1.01, close * 0.98, close))

    entropy = compute_entropy_approx(bars, lookback=20)
    assert entropy is not None
    assert 0.0 <= entropy <= 1.0


def test_enrich_daily_bars_populates_new_research_features() -> None:
    ticker = "AAA"
    bars = []
    close = 100.0
    for idx in range(70):
        date_str = f"2024-03-{idx+1:02d}" if idx < 31 else f"2024-04-{idx-30:02d}"
        gap = 0.002 if idx % 3 == 0 else -0.001
        open_ = close * (1.0 + gap)
        high = open_ * 1.02
        low = open_ * 0.99
        close = open_ * (1.0 + (0.004 if idx % 2 == 0 else -0.003))
        bars.append(_daily_bar(date_str, open_, high, low, close))

    trading_day = bars[-1]["date"]
    enriched = enrich_daily_bars({ticker: bars}, [trading_day])
    features = enriched[ticker][trading_day]

    assert features["entropy_20d"] is not None
    assert 0.0 <= features["entropy_20d"] <= 1.0
    assert features["atr_ratio_10_60"] is not None
    assert features["range_compression_10_60"] is not None
    assert features["gap_zscore_20d"] is not None


def test_gap_zscore_and_range_helpers_return_values() -> None:
    bars = []
    close = 50.0
    for idx in range(65):
        open_ = close * (1.0 + 0.002)
        high = open_ * 1.03
        low = open_ * 0.98
        close = open_ * 1.001
        bars.append(_daily_bar(f"2024-05-{idx+1:02d}", open_, high, low, close))

    assert compute_average_true_range(bars, 10) is not None
    assert compute_average_range(bars, 10) is not None
    assert compute_gap_zscore(bars[:-1], today_open=bars[-1]["open"], lookback=20) is not None
