"""The cockpit Overview is a registry-driven SSOT console: a DEPLOY section (the Bybit fund — the connected
backfill headline + its individual constituent edges, every row PAPER-badged) sits ABOVE a visually-dimmed
RESEARCH section ("not the fund") grouped by venue. Adding ONE registry entry makes a tracker auto-appear in
the correct section — the fast-track. All seeded in-memory (no DB, no net), leak-clean (relative URLs)."""
from __future__ import annotations

import datetime as dt
import random
import re

import fxhnt.adapters.persistence.forward_nav as forward_nav_mod
import fxhnt.application.dashboard_service as dashboard_mod
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.forward_models import ForwardNavRow as Row
from fxhnt.application.forward_models import ForwardSummary

_BYBIT_SLEEVES = ("crypto_tstrend", "unlock", "xsfunding", "positioning")


def _seeded_paper_repo() -> PaperRepo:
    """A PaperRepo carrying the precomputed bybit_sleeve_ret (4 sleeves, 300 days) the deploy
    individual-edges breakdown reads."""
    repo = PaperRepo("sqlite://")
    repo.migrate()
    at = dt.datetime(2026, 6, 24, 12, 0)
    base = dt.date(2026, 1, 1)
    for sleeve in _BYBIT_SLEEVES:
        rng = random.Random(hash(sleeve) & 0xFFFF)
        for i in range(300):
            d = (base + dt.timedelta(days=i)).isoformat()
            repo.upsert_bybit_sleeve_ret(d, sleeve, 0.001 + rng.gauss(0.0, 0.02), at=at)
    return repo


def _bybit_backfill_repo() -> PaperRepo:
    """A PaperRepo with a multi-year backfill nav ending $393,955 (+294%) — the headline record."""
    repo = PaperRepo("sqlite://")
    repo.migrate()
    at = dt.datetime(2026, 6, 24, 12, 0)
    pts = [100_000.0, 140_000.0, 120_000.0, 210_000.0, 300_000.0, 360_000.0, 393_955.0]
    for i, eq in enumerate(pts):
        rd = dt.date(2021, 1, 1) + dt.timedelta(days=120 * i)
        repo.upsert_nav(rd.isoformat(), capital=100_000.0, realized=0.0, unrealized=0.0, equity=eq, at=at)
    return repo


def _client() -> TestClient:
    fwd = ForwardNavRepo("sqlite://")
    fwd.migrate()
    at = dt.datetime(2026, 6, 24, 12, 0)
    # a short pre-gate bybit_4edge forward track so the headline gate chip + dimming render
    rows = [Row("bybit_4edge", f"2026-06-{5 + i:02d}", 0.004, 1.0 + 0.004 * (i + 1)) for i in range(8)]
    fwd.upsert_rows(rows, at)
    fwd.upsert_summary(ForwardSummary("bybit_4edge", "2026-06-12", 8, 1.032, 0.032, 1.4, -0.02),
                       "WAIT", "building (8/14 days)", at)
    return TestClient(create_app(fwd, paper_repo=_seeded_paper_repo(),
                                 bybit_paper_repo=_bybit_backfill_repo()))


# ---- DEPLOY section: the fund headline + constituent edges + PAPER badge -------------------------------

def test_deploy_section_leads_with_bybit_backfill_total() -> None:
    r = _client().get("/")
    assert r.status_code == 200
    assert "Deploy" in r.text and "the fund" in r.text
    # the connected backfill headline, not a not-started 0.00
    assert "393,955" in r.text
    assert "+294%" in r.text or "+293.96%" in r.text


def test_deploy_section_shows_individual_constituent_edges() -> None:
    # unlock/xsfunding/positioning still have a standalone STRATEGY_REGISTRY entry, so their raw sleeve id
    # shows up in their working `/strategy/{id}` link. crypto_tstrend's standalone entry was retired (Phase
    # 0b Task 4) — it renders its own human display name (from `_SLEEVE_DISPLAY_NAMES`) instead of the raw
    # id, with no dead link (see test_paper_web.py::test_crypto_tstrend_edge_gets_human_name_and_no_dead_link).
    from fxhnt.application.bybit_book_eval import _SLEEVE_DISPLAY_NAMES

    r = _client().get("/")
    body = r.text
    deploy = body.split("Research", 1)[0]   # the deploy section is everything above the research heading
    for sleeve in ("unlock", "xsfunding", "positioning"):
        assert sleeve in deploy, f"constituent {sleeve} missing from the deploy section"
    assert _SLEEVE_DISPLAY_NAMES["crypto_tstrend"] in deploy, "crypto_tstrend constituent missing its human name"


def test_every_track_has_paper_badge() -> None:
    body = _client().get("/").text
    assert "PAPER" in body
    assert "badge-exec paper" in body
    # no live execution today
    assert "badge-exec live" not in body


def test_deploy_before_research() -> None:
    body = _client().get("/").text
    assert body.index("Deploy") < body.index("Research")


# ---- RESEARCH section: dimmed, grouped by venue --------------------------------------------------------

def test_research_section_is_dimmed_and_present() -> None:
    body = _client().get("/").text
    assert "section research" in body          # the dimmed wrapper class
    assert "not the fund" in body
    # a couple of research trackers render (no forward summary here -> still listed, warming)
    assert "Token-unlock dilution shorts" in body


def test_research_grouped_by_venue() -> None:
    body = _client().get("/").text
    research = body.split("not the fund", 1)[1]
    # venue subheads appear (registry venues), e.g. the ibkr-equity research trackers
    assert "ibkr-equity" in research


# ---- the FAST-TRACK: one registry entry = it auto-appears in the correct section -----------------------

def _client_with_synthetic(monkeypatch, sid: str, entry: dict) -> TestClient:
    """Add a synthetic registry entry, then build a fresh app (migrate re-seeds the DB registry from the
    Python SSOT). Patches the registry the repo AND the service read so the new row flows end-to-end."""
    patched = dict(forward_nav_mod.STRATEGY_REGISTRY)
    patched[sid] = entry
    monkeypatch.setattr(forward_nav_mod, "STRATEGY_REGISTRY", patched)
    monkeypatch.setattr(dashboard_mod, "STRATEGY_REGISTRY", patched)
    fwd = ForwardNavRepo("sqlite://")
    fwd.migrate()
    return TestClient(create_app(fwd, bybit_paper_repo=_bybit_backfill_repo()))


def test_fast_track_research_entry_auto_appears(monkeypatch) -> None:
    entry = {"display_name": "Synthetic research probe", "sleeve": "synthetic",
             "venue": "kraken-spot",
             "tier": "research", "execution": "paper",
             "gate_spec": {"gate_type": "reconciliation", "min_forward_days": 14}}
    body = _client_with_synthetic(monkeypatch, "synth_research", entry).get("/").text
    research = body.split("not the fund", 1)[1]
    assert "Synthetic research probe" in research
    assert "kraken-spot" in research          # its (new) venue group auto-rendered


def test_fast_track_deploy_entry_auto_appears(monkeypatch) -> None:
    entry = {"display_name": "Synthetic deploy probe", "sleeve": "synthetic",
             "venue": "bybit-perp",
             "tier": "deploy", "execution": "paper",
             "gate_spec": {"gate_type": "reconciliation", "min_forward_days": 14}}
    body = _client_with_synthetic(monkeypatch, "synth_deploy", entry).get("/").text
    deploy = body.split("Research", 1)[0]
    assert "Synthetic deploy probe" in deploy


# ---- leak-clean (protects the nginx tailnet proxy) ----------------------------------------------------

_ABS_URL = re.compile(r"https?://(?!www\.w3\.org/)[^\s\"'<>]+")


def test_overview_is_leak_clean() -> None:
    hits = _ABS_URL.findall(_client().get("/").text)
    assert not hits, f"leak-clean violation: {hits}"
