"""Cockpit Overview redesign: the fund headline is CONNECTED to the bybit book's multi-year BACKFILL record
(real Sharpe/return, never a not-started 0.00), short pre-gate forward Sharpes are kept but visibly DIMMED,
the clutter (prose paragraphs + duplicate footer nav) is gone, and every page stays leak-clean (relative
URLs only) so the nginx tailnet proxy never exposes the internal address. All seeded in-memory — no DB, no
net."""
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.forward_models import BacktestSummary
from fxhnt.application.forward_models import ForwardNavRow as Row
from fxhnt.application.forward_models import ForwardSummary

# Every nav link in base.html. Each must resolve 200 (forward track empty is fine — routes degrade).
_NAV_LINKS = ["/", "/paper", "/paper/replay", "/paper/sim"]


def _bybit_backfill_repo() -> PaperRepo:
    """A bybit-venue PaperRepo seeded with a multi-year BACKFILL nav series ending at $393,955 (+294% off
    $100k). In-memory shared cache so the same DB is visible to the app's own repo handle."""
    repo = PaperRepo("sqlite://")
    repo.migrate()
    at = dt.datetime(2026, 6, 24, 12, 0)
    # A coarse multi-year climb 100k -> 393,955 (monotone-ish with one dip so maxDD is non-zero).
    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(*, seed_headline: bool = True, seed_backfill: bool = True) -> TestClient:
    repo = ForwardNavRepo("sqlite://")
    repo.migrate()
    if seed_headline:
        at = dt.datetime(2026, 6, 24, 12, 0)
        # A SHORT (pre-gate) bybit_4edge forward track: 8 days < the 21-day reconciliation gate window, so
        # its forward Sharpe is statistically provisional and must render DIMMED.
        rows = [Row("bybit_4edge", f"2026-06-{5 + i:02d}", 0.004, 1.0 + 0.004 * (i + 1)) for i in range(8)]
        repo.upsert_rows(rows, at)
        repo.upsert_summary(
            ForwardSummary("bybit_4edge", "2026-06-12", 8, 1.032, 0.032, 1.4, -0.02),
            "WAIT", "building (8/21 days)", at)
        repo.upsert_backtest_summary(BacktestSummary(
            "bybit_4edge", "2026-05-30", cagr=0.30, ann_vol=0.20, sharpe=1.5, max_drawdown=-0.16,
            passed=True, dsr=0.7, is_sharpe=1.6, oos_sharpe=1.3, pvalue=0.01), at)
    bybit_repo = _bybit_backfill_repo() if seed_backfill else None
    return TestClient(create_app(repo, bybit_paper_repo=bybit_repo))


# ---- single tidy nav (no grouped Live/Backtest labels, no duplicate footer) ---------------------------

def test_nav_is_single_tidy_row() -> None:
    r = _client().get("/")
    assert r.status_code == 200
    for label in ("Overview", "Paper", "Replay", "Backtest"):
        assert label in r.text, f"nav label {label!r} missing"


def test_every_nav_link_resolves() -> None:
    c = _client()
    for href in _NAV_LINKS:
        assert c.get(href).status_code == 200, f"nav link {href} did not resolve"


def test_no_duplicate_footer_nav() -> None:
    # The old "Go to" footer (a second copy of the Paper/Replay/Backtest links) is removed.
    assert "Go to" not in _client().get("/").text


def test_nav_is_hx_boosted() -> None:
    # The nav bar is hx-boost'ed so links swap the body via htmx (SPA-smooth) instead of full page loads.
    # Verified in a real browser: the per-page IIFEs (replay scrubber, sim form/result) re-init after a
    # boosted swap because htmx 1.x runs inline <script> tags in swapped content.
    txt = _client().get("/").text
    assert '<nav class="bar" hx-boost="true">' in txt


# ---- headline connected to the bybit BACKFILL record --------------------------------------------------

def test_headline_shows_backfill_record_not_zero() -> None:
    r = _client().get("/")
    assert r.status_code == 200
    # The COMPOUNDED backfill, not the not-started forward track: $393,955 / +294% / a REAL Sharpe.
    assert "393,955" in r.text
    assert "+294%" in r.text or "+294.0%" in r.text or "+293.96%" in r.text
    # Never lead with the not-started forward Sharpe / 0 forward days.
    assert "0 forward days" not in r.text
    assert "Sharpe 0.00" not in r.text and "Sharpe ~0.00" not in r.text


def test_headline_renders_backfill_curve() -> None:
    r = _client().get("/")
    assert "<svg" in r.text and "<polyline" in r.text   # the multi-year nav curve, inline SVG, no JS lib


def test_headline_shows_live_gate_chip() -> None:
    r = _client().get("/")
    # The small secondary readiness chip: gate days/min + status (the forward track is what is WAITing).
    assert "8/21" in r.text and "WAIT" in r.text


def test_headline_degrades_without_backfill() -> None:
    # No backfill seeded: home must still render (never 500) and not invent a record.
    r = _client(seed_backfill=False).get("/")
    assert r.status_code == 200
    assert "Fund status" in r.text


# ---- provisional (pre-gate) forward Sharpes are kept but DIMMED ---------------------------------------

def test_pregate_forward_sharpe_is_dimmed_provisional() -> None:
    r = _client().get("/")
    assert r.status_code == 200
    # The pre-gate marker is present (kept "to watch" but visibly not authoritative) and a caption explains it.
    assert "pre-gate" in r.text
    assert "provisional" in r.text.lower()


def test_edges_table_present_with_links() -> None:
    r = _client().get("/")
    # The SSOT console groups deploy (the fund) and research (not the fund); both render fleet tables with
    # /strategy links.
    assert "Deploy" in r.text and "Research" in r.text
    assert 'class="fleet"' in r.text
    assert "/strategy/" in r.text


# ---- declutter: the internal "class <sleeve>" dev row is gone, Research is collapsed ------------------

def test_no_class_sleeve_dev_row() -> None:
    # The edge cards used to render the internal sleeve id as "class <sleeve_name>" (on mobile the fleet
    # table reflows to cards and the data-label="class" cell read e.g. "class crypto_tstrend"). That's dev
    # info, not user info — removed. The human title + real metrics (Sharpe/return/maxDD) stay.
    txt = _client().get("/").text
    assert 'data-label="class"' not in txt
    assert ">class<" not in txt
    # the real metrics + human-facing structure remain
    assert "Sharpe" in txt and "maxDD" in txt


def test_research_section_is_collapsed_details() -> None:
    # The long "Research — not the fund" block is collapsible (collapsed by default) so the main fund
    # overview isn't a wall of experimental edges; the content is intact inside the <details>.
    txt = _client().get("/").text
    assert "<details>" in txt
    # the summary clearly labels the collapsed section
    assert "<summary" in txt
    assert "not the fund (experimental edges)" in txt
    # the research table is still inside (just collapsed), not deleted
    assert 'id="research-fleet"' in txt


# ---- clutter cut: the two prose paragraphs are gone ---------------------------------------------------

def test_prose_paragraphs_removed() -> None:
    txt = _client().get("/").text
    assert "Forward track not started yet" not in txt
    assert "is a Phase-1 paper-forward verdict" not in txt
    assert "too little history to chart" not in txt


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

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


def _assert_leak_clean(html: str) -> None:
    hits = _ABS_URL.findall(html)
    assert not hits, f"leak-clean violation: absolute URLs in rendered HTML: {hits}"


def test_home_is_leak_clean() -> None:
    _assert_leak_clean(_client().get("/").text)


def test_base_chrome_is_leak_clean() -> None:
    c = _client()
    for href in _NAV_LINKS:
        _assert_leak_clean(c.get(href).text)
