"""ingest_bybit_worst_basis — per symbol compute the daily 1m worst_basis series and write the `worst_basis`
feature into a bybit_features TimescaleFeatureStore.

Date-resumable (each symbol resumes from its last persisted `worst_basis` day), concurrent (all symbols
fetched once via the shared pool), bounded-memory (one symbol at a time). The Bybit adapter is a fake — NO HTTP.
"""
from __future__ import annotations

from fxhnt.adapters.warehouse.timescale_feature_store import TimescaleFeatureStore
from fxhnt.application.bybit_worst_basis_ingest import ingest_bybit_worst_basis

_DAY = 86_400


class _FakeBybit:
    """Stand-in for BybitKlines.intraday_worst_basis: returns a canned {epoch_day: worst_basis} per symbol."""

    def __init__(self, panels: dict[str, dict[int, float]]) -> None:
        self._panels = panels
        self.calls: list[tuple[str, int | None]] = []   # (symbol, start_ms) — to assert date-resume

    def intraday_worst_basis(self, symbol: str, *, start_ms=None, interval: str = "1") -> dict[int, float]:
        self.calls.append((symbol, start_ms))
        return self._panels.get(symbol, {})


def test_ingest_writes_worst_basis_feature() -> None:
    store = TimescaleFeatureStore("sqlite://", table="bybit_features")
    bybit = _FakeBybit({
        "BTCUSDT": {19000: -0.05, 19001: -0.20},
        "ETHUSDT": {19000: -0.01},
        "DEADUSDT": {},   # no overlap days -> nothing written
    })
    summary = ingest_bybit_worst_basis(store, bybit, ["BTCUSDT", "ETHUSDT", "DEADUSDT"])

    assert store.read_panel(["BTCUSDT"], "worst_basis") == {"BTCUSDT": {19000: -0.05, 19001: -0.20}}
    assert store.read_panel(["ETHUSDT"], "worst_basis") == {"ETHUSDT": {19000: -0.01}}
    assert store.read_panel(["DEADUSDT"], "worst_basis") == {"DEADUSDT": {}}

    assert summary["symbols"] == 2          # symbols that produced >=1 worst_basis day
    assert summary["days"] == 3             # 2 (BTC) + 1 (ETH)
    assert summary["worst"] == -0.20        # the single deepest observed
    store.close()


def test_ingest_is_idempotent() -> None:
    store = TimescaleFeatureStore("sqlite://", table="bybit_features")
    bybit = _FakeBybit({"BTCUSDT": {19000: -0.05, 19001: -0.20}})
    ingest_bybit_worst_basis(store, bybit, ["BTCUSDT"])
    first = store.read_panel(["BTCUSDT"], "worst_basis")
    ingest_bybit_worst_basis(store, bybit, ["BTCUSDT"], resume=False)   # re-run
    assert store.read_panel(["BTCUSDT"], "worst_basis") == first        # upsert in place, no dupes
    store.close()


def test_resume_advances_done_symbols_from_last_date_and_completes_the_rest() -> None:
    """DATE-RESUME (replaces symbol-skip): A,B already carry OLD worst_basis -> re-computed FROM THEIR LAST
    PERSISTED DAY so NEW days land (advance); C,D back-filled from the default floor. Nothing is skipped, so
    the heavy weekly job advances every run."""
    store = TimescaleFeatureStore("sqlite://", table="bybit_features")
    ingest_bybit_worst_basis(store, _FakeBybit({"AUSDT": {19000: -0.1}, "BUSDT": {19000: -0.2}}),
                             ["AUSDT", "BUSDT"])

    panels = {"AUSDT": {19000: -0.1, 19001: -0.15}, "BUSDT": {19000: -0.2, 19001: -0.25},
              "CUSDT": {19000: -0.3}, "DUSDT": {19000: -0.4}}
    second = _FakeBybit(panels)
    seen: list[str] = []
    ingest_bybit_worst_basis(
        store, second, ["AUSDT", "BUSDT", "CUSDT", "DUSDT"], resume=True,
        progress=lambda m: seen.append(m))
    fetched = dict(second.calls)
    assert set(fetched) == {"AUSDT", "BUSDT", "CUSDT", "DUSDT"}
    assert fetched["AUSDT"] == 19000 * 86_400_000 and fetched["BUSDT"] == 19000 * 86_400_000
    assert fetched["CUSDT"] is None and fetched["DUSDT"] is None
    assert any("resuming from last persisted date" in m for m in seen)

    # done symbols ADVANCED to the new day; the rest were back-filled.
    full = store.read_panel(["AUSDT", "BUSDT", "CUSDT", "DUSDT"], "worst_basis")
    assert full == panels
    store.close()


def test_no_resume_refetches_everything() -> None:
    store = TimescaleFeatureStore("sqlite://", table="bybit_features")
    panels = {"AUSDT": {19000: -0.1}}
    ingest_bybit_worst_basis(store, _FakeBybit(panels), ["AUSDT"])
    second = _FakeBybit(panels)
    ingest_bybit_worst_basis(store, second, ["AUSDT"], resume=False)
    assert second.calls == [("AUSDT", None)]
    store.close()


def test_concurrent_ingest_writes_all_symbols_exactly_once() -> None:
    store = TimescaleFeatureStore("sqlite://", table="bybit_features")
    panels = {f"S{i}USDT": {19000: -0.01 * (i + 1)} for i in range(25)}
    bybit = _FakeBybit(panels)
    summary = ingest_bybit_worst_basis(store, bybit, list(panels), workers=8)
    assert summary["symbols"] == 25
    assert sorted(c[0] for c in bybit.calls) == sorted(panels)   # each symbol fetched exactly once
    assert store.read_panel(list(panels), "worst_basis") == panels
    store.close()


def test_ingest_isolated_from_default_features_table() -> None:
    import os
    import tempfile
    with tempfile.TemporaryDirectory() as td:
        fdsn = f"sqlite:///{os.path.join(td, 'shared.db')}"
        default = TimescaleFeatureStore(fdsn)
        bybit_store = TimescaleFeatureStore(fdsn, table="bybit_features")
        default.write_features("BTCUSDT", [(19000 * _DAY, {"close": 42.0})])
        ingest_bybit_worst_basis(bybit_store, _FakeBybit({"BTCUSDT": {19000: -0.1}}), ["BTCUSDT"])
        assert default.read_panel(["BTCUSDT"], "worst_basis") == {"BTCUSDT": {}}
        assert bybit_store.read_panel(["BTCUSDT"], "worst_basis") == {"BTCUSDT": {19000: -0.1}}
        default.close()
        bybit_store.close()
