from __future__ import annotations

from datetime import datetime, timezone
from types import SimpleNamespace

import polars as pl

from uforia.microstructure.live import build_live_microstructure_bars
from uforia.storage import DatasetName


def test_build_live_bars_uses_db_trade_aggregation_without_raw_event_scan(monkeypatch) -> None:
    start = datetime(2026, 3, 17, 18, 0, tzinfo=timezone.utc)
    end = datetime(2026, 3, 17, 18, 5, tzinfo=timezone.utc)
    calls: list[DatasetName] = []

    book = pl.DataFrame(
        {
            "ts": [start, start.replace(minute=1)],
            "date": [start.date(), start.date()],
            "asset": ["BTC", "BTC"],
            "venue": ["binance_spot", "binance_spot"],
            "mid_price": [74000.0, 74100.0],
            "microprice": [74001.0, 74101.0],
            "spread": [1.0, 1.5],
            "depth_bid_5": [10.0, 12.0],
            "depth_ask_5": [11.0, 13.0],
            "is_gap_detected": [False, False],
        }
    )

    class DummyStore:
        db = SimpleNamespace(settings=SimpleNamespace(dsn="postgresql://example"))

        def read(self, dataset, **kwargs):  # type: ignore[no-untyped-def]
            calls.append(dataset)
            if dataset == DatasetName.MICROSTRUCTURE_BOOK_STATE:
                return book
            raise AssertionError(f"unexpected dataset read: {dataset}")

    monkeypatch.setattr(
        "uforia.microstructure.live._query_live_trade_agg_rows",
        lambda *args, **kwargs: [
            {
                "bucket_ts": start,
                "asset": "BTC",
                "venue": "binance_spot",
                "trade_open": 74010.0,
                "trade_high": 74080.0,
                "trade_low": 74005.0,
                "trade_close": 74050.0,
                "vwap": 74040.0,
                "volume": 25.0,
                "buy_volume": 15.0,
                "sell_volume": 10.0,
                "trade_count": 7,
            }
        ],
    )

    frame = build_live_microstructure_bars(DummyStore(), start, end, asset="BTC", bar_sizes=(60,))

    assert calls == [DatasetName.MICROSTRUCTURE_BOOK_STATE]
    matched = frame.filter(pl.col("volume") == 25.0)
    assert frame.height == 2
    assert matched.height == 1
    assert matched.item(0, "trade_count") == 7
    assert matched.item(0, "quality_flag") == "ok"
    assert matched.item(0, "open") == 74010.0
    assert matched.item(0, "high") == 74080.0
    assert matched.item(0, "low") == 74005.0
    assert matched.item(0, "close") == 74050.0


def test_build_live_bars_falls_back_to_book_prices_when_trades_missing(monkeypatch) -> None:
    start = datetime(2026, 3, 17, 18, 0, tzinfo=timezone.utc)
    end = datetime(2026, 3, 17, 18, 5, tzinfo=timezone.utc)

    book = pl.DataFrame(
        {
            "ts": [start, start.replace(minute=1)],
            "date": [start.date(), start.date()],
            "asset": ["BTC", "BTC"],
            "venue": ["binance_spot", "binance_spot"],
            "mid_price": [74000.0, 74100.0],
            "microprice": [74001.0, 74101.0],
            "spread": [1.0, 1.5],
            "depth_bid_5": [10.0, 12.0],
            "depth_ask_5": [11.0, 13.0],
            "is_gap_detected": [False, False],
        }
    )

    class DummyStore:
        db = SimpleNamespace(settings=SimpleNamespace(dsn="postgresql://example"))

        def read(self, dataset, **kwargs):  # type: ignore[no-untyped-def]
            if dataset == DatasetName.MICROSTRUCTURE_BOOK_STATE:
                return book
            raise AssertionError(f"unexpected dataset read: {dataset}")

    monkeypatch.setattr("uforia.microstructure.live._query_live_trade_agg_rows", lambda *args, **kwargs: [])

    frame = build_live_microstructure_bars(DummyStore(), start, end, asset="BTC", bar_sizes=(60,))
    matched = frame.filter(pl.col("ts") == start)

    assert matched.height == 1
    assert matched.item(0, "quality_flag") == "book_only"
    assert matched.item(0, "open") == 74000.0
    assert matched.item(0, "high") == 74000.0
    assert matched.item(0, "low") == 74000.0
    assert matched.item(0, "close") == 74000.0
