"""CLI `fxhnt bybit-ingest-klines` — universe build + perp/spot daily-close ingest into bybit_features.
NO network: the BybitKlines + BybitFunding (universe) adapters are monkeypatched with fakes, and the
operational DSN points at a temp sqlite db."""
from __future__ import annotations

from typer.testing import CliRunner

from fxhnt.cli import app


class _FakeUniverse:
    def active_universe(self):
        return ["BTCUSDT", "ETHUSDT"]

    def ever_listed_universe(self, *a, **k):
        return ["BTCUSDT", "ETHUSDT", "LUNAUSDT"]  # +1 delisted


class _FakeKlines:
    def daily_ohlcv(self, symbol, *, category, start_ms=None):
        # perp slightly above spot; both produce 2 day-rows with a full OHLCV bar.
        base = 100.0 if category == "linear" else 99.5
        return {d: {"close": base + i, "high": base + i + 1.0, "low": base + i - 1.0,
                    "turnover": (base + i) * 1000.0}
                for i, d in enumerate((19000, 19001))}


def _patch(monkeypatch, tmp_path):
    dsn = f"sqlite:///{tmp_path / 'op.db'}"
    monkeypatch.setenv("FXHNT_OPERATIONAL_DSN", dsn)
    from fxhnt import cli as climod
    climod.get_settings.cache_clear()
    monkeypatch.setattr("fxhnt.adapters.data.bybit_funding.BybitFunding", lambda *a, **k: _FakeUniverse())
    monkeypatch.setattr("fxhnt.adapters.data.bybit_klines.BybitKlines", lambda *a, **k: _FakeKlines())
    return dsn


def test_cli_active_only(monkeypatch, tmp_path):
    dsn = _patch(monkeypatch, tmp_path)
    res = CliRunner().invoke(app, ["bybit-ingest-klines", "--symbols", "all"])
    assert res.exit_code == 0, res.output
    assert "2 symbols (active)" in res.output
    assert "done: 2 symbols" in res.output
    assert "oldest date 2022-01-08" in res.output  # epoch_day 19000

    from fxhnt.adapters.warehouse.timescale_feature_store import TimescaleFeatureStore
    store = TimescaleFeatureStore(dsn, table="bybit_features")
    assert store.crypto_close_panel() == {
        "BTCUSDT": {19000: 100.0, 19001: 101.0},
        "ETHUSDT": {19000: 100.0, 19001: 101.0},
    }
    assert store.crypto_spot_panel() == {
        "BTCUSDT": {19000: 99.5, 19001: 100.5},
        "ETHUSDT": {19000: 99.5, 19001: 100.5},
    }
    store.close()


def test_cli_include_delisted(monkeypatch, tmp_path):
    _patch(monkeypatch, tmp_path)
    res = CliRunner().invoke(app, ["bybit-ingest-klines", "--symbols", "all", "--include-delisted"])
    assert res.exit_code == 0, res.output
    assert "3 symbols (ever-listed)" in res.output
    assert "LUNAUSDT" in res.output  # the delisted name was ingested (survivorship-free)


def test_cli_explicit_symbols_and_from(monkeypatch, tmp_path):
    _patch(monkeypatch, tmp_path)
    res = CliRunner().invoke(app, ["bybit-ingest-klines", "--symbols", "BTCUSDT", "--from", "2022-01"])
    assert res.exit_code == 0, res.output
    assert "1 symbols (explicit)" in res.output
    assert "floor=2022-01" in res.output


def test_cli_bad_from_month(monkeypatch, tmp_path):
    _patch(monkeypatch, tmp_path)
    res = CliRunner().invoke(app, ["bybit-ingest-klines", "--symbols", "BTCUSDT", "--from", "2022"])
    assert res.exit_code == 1
    assert "must be YYYY-MM" in res.output
