"""The `fxhnt bybit-paper-backfill` CLI: walks the full Bybit history and persists the live eq-wt 4-edge book
daily into the operational DB. NO network (in-memory bybit_features seeded into a
file-sqlite DSN shared by the warehouse store + the paper repo; the unlock loader is stubbed to the seeded
calendar). Proves the command wires args + the incremental resume, and writes a multi-day venue=bybit curve."""
from __future__ import annotations

from typer.testing import CliRunner

import fxhnt.application.bybit_liquidity as bybit_liquidity
import fxhnt.application.unlock_calendar_loader as unlock_loader
import fxhnt.cli as cli
from fxhnt.adapters.persistence.paper_repo import PaperRepo
from fxhnt.adapters.warehouse.timescale_feature_store import TimescaleFeatureStore

runner = CliRunner()
_DAY = 86_400


def _seed(store: TimescaleFeatureStore, *, days: int = 200) -> list[dict]:
    import math as _m
    syms = ["BTCUSDT", "AAAUSDT", "BBBUSDT", "CCCUSDT"]
    px = {s: 100.0 for s in syms}
    px["BTCUSDT"] = 50_000.0
    for d in range(days):
        crowd = (d % 3 == 0)
        for s in syms:
            drift = {"BTCUSDT": 0.001, "AAAUSDT": 0.002, "BBBUSDT": -0.001, "CCCUSDT": 0.0015}[s]
            px[s] *= (1.0 + drift + 0.003 * _m.sin(d / 5.0 + hash(s) % 7))
            ratio = (0.70 if crowd else 0.30) if s == "AAAUSDT" else \
                    (0.30 if crowd else 0.70) if s == "BBBUSDT" else 0.50
            store.write_features(s, [(d * _DAY, {
                "close": px[s], "funding": 0.0005, "spot_close": px[s] * 1.0002, "long_ratio": ratio})])
    return [{"sym": "AAA", "cliff_day": days - 5, "n": 5_000_000.0, "cat": "insiders"}]


class _settings:
    def __init__(self, dsn: str) -> None:
        self.operational_dsn = dsn
        self.paper_capital = 100_000.0
        self.paper_enabled = True


def _wire(tmp_path, monkeypatch) -> str:
    """Seed bybit_features into a file-sqlite DB, stub settings + the unlock loader, return the DSN."""
    dsn = f"sqlite:///{tmp_path / 'cockpit.db'}"
    store = TimescaleFeatureStore(dsn, table="bybit_features")
    events = _seed(store)
    store.close()
    monkeypatch.setattr(cli, "get_settings", lambda: _settings(dsn))
    # Full universe (no liquid floor) so the seed's symbols all participate, and the seeded calendar.
    monkeypatch.setattr(bybit_liquidity, "liquid_universe", lambda *a, **k: set())
    monkeypatch.setattr(unlock_loader, "operational_unlock_repo", lambda s: None)
    monkeypatch.setattr(unlock_loader, "load_unlock_events", lambda repo, path: events)
    return dsn


def test_cli_persists_multi_day_bybit_curve(tmp_path, monkeypatch):
    dsn = _wire(tmp_path, monkeypatch)
    result = runner.invoke(cli.app, ["bybit-paper-backfill", "--min-dollar-vol", "0"])
    assert result.exit_code == 0, result.output
    assert "venue=bybit" in result.output

    repo = PaperRepo(dsn)
    hist = repo.nav_history()
    assert len(hist) > 1, "expected a multi-day venue=bybit nav series"
    dates = [p.run_date for p in hist]
    assert dates == sorted(dates) == sorted(set(dates))


def test_cli_incremental_resume_is_noop_then_rebuild_matches(tmp_path, monkeypatch):
    dsn = _wire(tmp_path, monkeypatch)

    r1 = runner.invoke(cli.app, ["bybit-paper-backfill", "--min-dollar-vol", "0"])
    assert r1.exit_code == 0, r1.output
    hist1 = [(p.run_date, p.equity) for p in PaperRepo(dsn).nav_history()]
    assert len(hist1) > 1

    # Default (incremental): nothing new after the last persisted day → persists 0, curve unchanged.
    r2 = runner.invoke(cli.app, ["bybit-paper-backfill", "--min-dollar-vol", "0"])
    assert r2.exit_code == 0, r2.output
    assert "persisted 0 day(s) (incremental)" in r2.output
    hist2 = [(p.run_date, p.equity) for p in PaperRepo(dsn).nav_history()]
    assert hist2 == hist1

    # --rebuild recomputes the whole history; idempotent → identical curve.
    r3 = runner.invoke(cli.app, ["bybit-paper-backfill", "--min-dollar-vol", "0", "--rebuild"])
    assert r3.exit_code == 0, r3.output
    hist3 = [(p.run_date, p.equity) for p in PaperRepo(dsn).nav_history()]
    assert hist3 == hist1, "rebuild diverged from the original (should be idempotent)"
