"""Cockpit /paper renders the LIVE Bybit book (per-symbol positions, trades, mark-to-market equity) — the
cockpit's only paper-book venue since Task 7a removed the Binance venue toggle. Bounded scroll boxes are
preserved; the page is leak-clean. NO network."""
from __future__ import annotations

import datetime as dt
import re

from fastapi.testclient import TestClient

from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
from fxhnt.adapters.persistence.paper_repo import PaperRepo
from fxhnt.adapters.web.app import create_app
from fxhnt.application.paper_book import PaperBookService
from fxhnt.ports.live_price import FakeLivePrice

_AT = dt.datetime(2026, 6, 24, tzinfo=dt.UTC)


def _seeded_bybit_live_repo() -> PaperRepo:
    """A bybit PaperRepo holding a real live book: two sleeves, three per-symbol lots."""
    repo = PaperRepo("sqlite://")
    repo.migrate()
    PaperBookService(repo, capital=100_000.0).derive_and_persist(
        run_date="2026-06-24",
        sleeve_weights={"crypto_tstrend": 1.0, "positioning": 1.0},
        symbol_weights_by_sleeve={
            "crypto_tstrend": {"BTCUSDT": 0.25},
            "positioning": {"AAAUSDT": 0.10, "BBBUSDT": -0.10}},
        prices={"BTCUSDT": 50_000.0, "AAAUSDT": 2.0, "BBBUSDT": 3.0},
        at=_AT)
    return repo


def _client(bybit_live) -> TestClient:
    fwd = ForwardNavRepo("sqlite://")
    fwd.migrate()
    binance = PaperRepo("sqlite://")           # a separate, empty repo — unused by any assertion here
    binance.migrate()
    return TestClient(create_app(
        fwd, paper_repo=binance,
        bybit_paper_repo=_seeded_bybit_live_repo(), bybit_live=bybit_live))


def test_bybit_view_renders_live_positions_and_trades() -> None:
    # Bump prices so the live mark moves equity off capital.
    c = _client(FakeLivePrice({"BTCUSDT": 51_000.0, "AAAUSDT": 2.1, "BBBUSDT": 2.9}))
    r = c.get("/paper/crypto?venue=bybit")
    assert r.status_code == 200
    assert "Bybit 4-edge book" in r.text and ">BYBIT<" in r.text
    # Per-symbol LIVE positions are shown (not just a returns table).
    for sym in ("BTCUSDT", "AAAUSDT", "BBBUSDT"):
        assert sym in r.text
    assert "open positions" in r.text
    assert "paper equity" in r.text
    # Recent trades from the rebalance are shown.
    assert "recent trades" in r.text
    assert "BUY" in r.text or "SELL" in r.text


def test_bybit_view_uses_bounded_scroll_boxes() -> None:
    c = _client(FakeLivePrice({"BTCUSDT": 50_000.0, "AAAUSDT": 2.0, "BBBUSDT": 3.0}))
    r = c.get("/paper/crypto?venue=bybit")
    assert r.status_code == 200
    assert "max-height:420px" in r.text and "overflow-y:auto" in r.text


def test_bybit_live_fragment_renders_positions() -> None:
    c = _client(FakeLivePrice({"BTCUSDT": 51_000.0, "AAAUSDT": 2.0, "BBBUSDT": 3.0}))
    r = c.get("/paper/live?venue=bybit")
    assert r.status_code == 200
    assert "open positions" in r.text
    assert "BTCUSDT" in r.text


def test_stray_venue_query_param_still_renders_the_bybit_book() -> None:
    # The Binance venue toggle was removed in Task 7a — an old bookmarked `?venue=binance` link is simply
    # ignored; the page still renders the (only) Bybit book, not an empty/separate "Binance" view.
    c = _client(FakeLivePrice({"BTCUSDT": 51_000.0, "AAAUSDT": 2.1, "BBBUSDT": 2.9}))
    r = c.get("/paper/crypto?venue=binance")
    assert r.status_code == 200
    assert "Bybit 4-edge book" in r.text
    assert "Binance paper book" not in r.text
    assert "BTCUSDT" in r.text


def test_bybit_view_leak_clean() -> None:
    abs_url = re.compile(r"https?://(?!www\.w3\.org/)[^\s\"'<>]+")
    c = _client(FakeLivePrice({"BTCUSDT": 50_000.0, "AAAUSDT": 2.0, "BBBUSDT": 3.0}))
    r = c.get("/paper/crypto?venue=bybit")
    assert r.status_code == 200
    assert not abs_url.findall(r.text)


def _client_with_forward(gate_status: str, gate_reason: str, days: int) -> TestClient:
    """A bybit client whose forward_nav carries a bybit_4edge summary (so the reconciliation gate card
    renders) with the given verdict — to assert the card surfaces the new criteria legibly."""
    from fxhnt.application.forward_models import ForwardNavRow as Nav
    from fxhnt.application.forward_models import ForwardSummary
    fwd = ForwardNavRepo("sqlite://")
    fwd.migrate()
    start = dt.date(2026, 6, 1)
    rows = [Nav("bybit_4edge", (start + dt.timedelta(days=i)).isoformat(), 0.001, 1.0 + 0.001 * (i + 1))
            for i in range(days)]
    fwd.upsert_rows(rows, _AT.replace(tzinfo=None))
    fwd.upsert_summary(
        ForwardSummary("bybit_4edge", rows[-1].date, days, rows[-1].nav, rows[-1].nav - 1.0, 1.5, -0.02),
        gate_status, gate_reason, _AT.replace(tzinfo=None))
    binance = PaperRepo("sqlite://")
    binance.migrate()
    return TestClient(create_app(
        fwd, paper_repo=binance,
        bybit_paper_repo=_seeded_bybit_live_repo(),
        bybit_live=FakeLivePrice({"BTCUSDT": 50_000.0, "AAAUSDT": 2.0, "BBBUSDT": 3.0})))


def test_forward_card_shows_reconciliation_criteria_on_pass() -> None:
    c = _client_with_forward("PASS", "22d: +2.20%, reconciles with backtest, clean execution", 22)
    r = c.get("/paper/crypto?venue=bybit")
    assert r.status_code == 200
    assert "[PASS]" in r.text
    assert "~2wk window" in r.text and "22/21 days" in r.text
    assert "reconciles with backtest" in r.text
    assert "clean execution" in r.text
    # all three criteria met -> three check marks on the card.
    assert r.text.count("✓") >= 3


def test_forward_card_flags_divergence_on_wait() -> None:
    # The mirage on the card: WAIT past the window with the divergence reason -> reconcile criterion shows ✗.
    c = _client_with_forward(
        "WAIT", "forward diverging from backtest (Δ -40.00% below expected, band ±50.00%)", 20)
    r = c.get("/paper/crypto?venue=bybit")
    assert r.status_code == 200
    assert "[WAIT]" in r.text
    assert "diverging from backtest" in r.text
    assert "✗ reconciles with backtest" in r.text
    assert "✓ clean execution" in r.text          # only reconciliation failed; execution was clean
