"""Phase C — carry re-validation on Bybit data (the venue we trade).

Seeds a synthetic in-memory `TimescaleFeatureStore("sqlite://", table=...)` with funding + perp close +
spot close (the same features Phases A/B ingest), runs the carry pipeline, and asserts:
  * a positive-funding-dispersion book yields positive carry (sane metrics);
  * a `cost_bps` haircut lowers the metrics vs cost_bps=0;
  * `revalidate` returns the Bybit metrics + a verdict (True when the net Bybit Sharpe is positive, False
    when it is not — Phase 0b venue consolidation retired the Binance-baseline comparison this used to
    render);
  * an empty Bybit store degrades to verdict 'insufficient data' (no crash).
NO network — the store is in-memory SQLite.
"""
from __future__ import annotations

from fxhnt.adapters.warehouse.timescale_feature_store import TimescaleFeatureStore
from fxhnt.application.bybit_carry_revalidate import carry_metrics_from_store, revalidate

_DAY = 86_400


def _seed(store: TimescaleFeatureStore, *, days: int, funding_by_symbol: dict[str, float],
          flat_price: float = 100.0, wobble: float = 0.0) -> None:
    """Write `days` of funding + flat perp close + flat spot close for each symbol. Flat prices → basis 0,
    so the carry return is funding-only (hand-checkable). `funding_by_symbol[s]` is the per-day funding rate;
    a small deterministic `wobble` (a cosine, sign keyed to the symbol) adds day-to-day variation so the
    daily carry return has finite, non-degenerate variance (else a constant return gives a 0/0 Sharpe)."""
    import math

    for idx, (sym, fund) in enumerate(funding_by_symbol.items()):
        rows = []
        for d in range(days):
            f = fund + wobble * math.cos(0.5 * d + idx)
            rows.append((d * _DAY, {"funding": f, "close": flat_price, "spot_close": flat_price}))
        store.write_features(sym, rows)


def _positive_dispersion(store: TimescaleFeatureStore, *, days: int = 50) -> None:
    """3 high-funding coins (+0.001/day) + 3 low-funding coins (−0.001/day): a market-neutral book longs the
    high-funding leg and shorts the low-funding leg → both legs earn positive carry."""
    _seed(store, days=days, wobble=0.0002, funding_by_symbol={
        "AAAUSDT": 0.001, "BBBUSDT": 0.001, "CCCUSDT": 0.001,
        "XXXUSDT": -0.001, "YYYUSDT": -0.001, "ZZZUSDT": -0.001,
    })


def test_positive_dispersion_yields_positive_carry() -> None:
    store = TimescaleFeatureStore("sqlite://", table="bybit_features")
    _positive_dispersion(store)
    m = carry_metrics_from_store(store, cost_bps=0.0)
    store.close()

    assert m["days"] > 0
    assert m["symbols"] == 6
    assert m["total_return"] > 0.0          # long high-funding + short low-funding earns
    assert m["sharpe"] > 0.0
    assert m["max_dd"] <= 0.0               # drawdown is non-positive by construction
    assert m["first_day"] is not None and m["last_day"] is not None


def test_cost_bps_lowers_metrics() -> None:
    store = TimescaleFeatureStore("sqlite://", table="bybit_features")
    _positive_dispersion(store)
    gross = carry_metrics_from_store(store, cost_bps=0.0)
    netted = carry_metrics_from_store(store, cost_bps=20.0)
    store.close()

    assert netted["total_return"] <= gross["total_return"]
    assert netted["sharpe"] <= gross["sharpe"]
    # The cost is a strict haircut whenever there is any turnover (the first day forms the book).
    assert netted["total_return"] < gross["total_return"]


def test_revalidate_holds_when_bybit_carry_is_positive() -> None:
    bybit = TimescaleFeatureStore("sqlite://", table="bybit_features")
    _positive_dispersion(bybit)
    out = revalidate(bybit, bybit_cost_bps=5.5)
    bybit.close()

    assert "bybit" in out
    assert out["bybit"]["days"] > 0
    assert out["verdict"]["holds"] is True
    assert out["verdict"]["rule"] == "bybit.sharpe > 0"


def test_revalidate_fails_when_bybit_has_no_dispersion() -> None:
    bybit = TimescaleFeatureStore("sqlite://", table="bybit_features")
    # NO funding dispersion (all coins same funding) → market-neutral book nets ~0 carry, net of cost < 0.
    _seed(bybit, days=50, funding_by_symbol={
        "AAAUSDT": 0.0001, "BBBUSDT": 0.0001, "CCCUSDT": 0.0001,
        "XXXUSDT": 0.0001, "YYYUSDT": 0.0001, "ZZZUSDT": 0.0001,
    })
    out = revalidate(bybit, bybit_cost_bps=5.5)
    bybit.close()

    assert out["verdict"]["holds"] is False


def test_empty_bybit_store_insufficient_data() -> None:
    bybit = TimescaleFeatureStore("sqlite://", table="bybit_features")   # never seeded
    out = revalidate(bybit)
    bybit.close()

    assert out["bybit"]["days"] == 0
    assert out["bybit"]["symbols"] == 0
    assert out["verdict"]["holds"] is None
    assert out["verdict"]["reason"] == "insufficient data"
