"""D2.2/2.3 (Task 3) — DashboardService.detail() attaches the REAL IBKR paper-account section (holdings,
recent trades, account NAV series, the exec_vs_sim recon signal) for the two IBKR real-account books
(multistrat/vrp), sourced from `IbkrAccountRepo` (Task 2) + `ForwardNavRepo`. Every other strategy (and an
IBKR book with no captured account data yet) gets the StrategyDetail defaults — never a crash."""
from __future__ import annotations

import datetime as dt

from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
from fxhnt.adapters.persistence.ibkr_account_repo import IbkrAccountRepo
from fxhnt.application.dashboard_service import DashboardService
from fxhnt.application.exec_capture import FillDTO
from fxhnt.application.forward_models import ForwardSummary


def _seeded_forward(with_exec: bool = True) -> ForwardNavRepo:
    repo = ForwardNavRepo("sqlite://")
    repo.migrate()
    at = dt.datetime(2026, 7, 14, 12, 0)
    repo.upsert_summary(ForwardSummary("multistrat", "2026-07-14", 20, 1.038, 0.038, 0.9, -0.02),
                        "WAIT", "20/20 forward days", at)
    if with_exec:
        repo.upsert_summary(ForwardSummary("multistrat_exec", "2026-07-14", 20, 1.031, 0.031, 0.8, -0.02),
                            "WAIT", "20/20 forward days", at)
    return repo


def _seeded_ibkr() -> IbkrAccountRepo:
    repo = IbkrAccountRepo("sqlite://")
    repo.migrate()
    at1 = dt.datetime(2026, 7, 10, 15, 0)
    at2 = dt.datetime(2026, 7, 14, 15, 0)
    repo.replace_snapshot("2026-07-10", nlv=1_000_000.0, cash=1000.0, gross=999_000.0,
                          positions={"SPY": 100.0, "IEF": 200.0}, at=at1)
    repo.replace_snapshot("2026-07-14", nlv=1_031_000.0, cash=500.0, gross=1_030_500.0,
                          positions={"SPY": 100.0, "IEF": 200.0}, at=at2)
    repo.record_fills(strategy_id="multistrat", rebalance_id="2026-07-10",
                      fills=[FillDTO(symbol="SPY", side="BOT", qty=100.0, price=450.0, fee=1.0,
                                     status="Filled", shortfall_bps=0.0),
                             FillDTO(symbol="IEF", side="BOT", qty=200.0, price=95.0, fee=0.5,
                                     status="Filled", shortfall_bps=0.0)],
                      at=at1)
    return repo


def test_ibkr_account_book_gets_holdings_trades_nav_and_recon() -> None:
    svc = DashboardService(_seeded_forward(), _seeded_ibkr())
    d = svc.detail("multistrat")
    assert d is not None
    assert d.recon is not None and d.recon.has_exec is True
    assert round(d.recon.divergence, 4) == round(0.031 - 0.038, 4)
    assert d.account_nav == [("2026-07-10", 1_000_000.0), ("2026-07-14", 1_031_000.0)]
    by_symbol = {h["symbol"]: h for h in d.holdings}
    assert by_symbol["SPY"]["description"] == "US stocks"
    assert by_symbol["SPY"]["value"] == 100.0 * 450.0
    assert round(sum(h["pct_of_book"] for h in d.holdings), 6) == 1.0
    assert len(d.recent_trades) == 2
    trade = next(t for t in d.recent_trades if t["symbol"] == "SPY")
    assert trade["action"] == "Buy"
    assert trade["cost_to_trade_pct"] == 0.0   # honest market-order shortfall, never fabricated


def test_pct_of_book_uses_gross_for_mixed_sign_holdings() -> None:
    """A book holding mixed-sign legs (a long leg + a short leg). A SIGNED denominator nets toward the small
    net and makes % blow past 100% / go negative; % of GROSS keeps each row's signed value but a sane [0,100]
    share that sums to ~100. Uses `multistrat` (a visible IBKR-account book) as the vehicle — vrp, the
    original defined-risk example, is ARCHIVED (detail() returns None), so it can no longer stand in here."""
    fwd = ForwardNavRepo("sqlite://")
    fwd.migrate()
    ibkr = IbkrAccountRepo("sqlite://")
    ibkr.migrate()
    at = dt.datetime(2026, 7, 14, 15, 0)
    # A long leg (+value) and a short leg (-value): if summed signed, total = +700 - 300 = +400, so the
    # short would render at -300/400 = -75% and the long at +175% — the bug. Gross = 1000.
    ibkr.replace_snapshot("2026-07-14", nlv=400.0, cash=0.0, gross=1000.0,
                          positions={"LONGX": 7.0, "SHORTX": -3.0}, at=at)
    ibkr.record_fills(strategy_id="multistrat", rebalance_id="r1",
                      fills=[FillDTO(symbol="LONGX", side="BOT", qty=7.0, price=100.0, fee=0.1,
                                     status="Filled", shortfall_bps=0.0),
                             FillDTO(symbol="SHORTX", side="SLD", qty=3.0, price=100.0, fee=0.1,
                                     status="Filled", shortfall_bps=0.0)],
                      at=at)
    d = DashboardService(fwd, ibkr).detail("multistrat")
    assert d is not None
    pcts = {h["symbol"]: h["pct_of_book"] for h in d.holdings}
    assert all(0.0 <= p <= 1.0 for p in pcts.values()), pcts
    assert abs(sum(pcts.values()) - 1.0) < 1e-9
    assert round(pcts["LONGX"], 4) == 0.70 and round(pcts["SHORTX"], 4) == 0.30
    # each row still carries its own SIGNED value (only the % denominator is gross).
    by_sym = {h["symbol"]: h for h in d.holdings}
    assert by_sym["LONGX"]["value"] == 700.0 and by_sym["SHORTX"]["value"] == -300.0
    # the stale-value caveat: each holding surfaces the date it was last marked.
    assert by_sym["LONGX"]["as_of"] == "2026-07-14"


def test_ibkr_account_book_with_no_captured_data_degrades_gracefully() -> None:
    empty_ibkr = IbkrAccountRepo("sqlite://")
    empty_ibkr.migrate()
    svc = DashboardService(_seeded_forward(with_exec=False), empty_ibkr)
    d = svc.detail("multistrat")
    assert d is not None
    assert d.recon is not None and d.recon.has_exec is False
    assert d.holdings == [] and d.recent_trades == [] and d.account_nav == []


def test_non_ibkr_strategy_has_no_account_section() -> None:
    svc = DashboardService(_seeded_forward(), _seeded_ibkr())
    d = svc.detail("unlock" if False else "sixtyforty")   # a plain registry strategy, no IBKR account
    assert d is not None
    assert d.recon is None
    assert d.holdings == [] and d.recent_trades == [] and d.account_nav == []


def test_no_ibkr_repo_wired_degrades_gracefully() -> None:
    svc = DashboardService(_seeded_forward())   # no ibkr_repo at all
    d = svc.detail("multistrat")
    assert d is not None
    assert d.recon is None
    assert d.holdings == [] and d.recent_trades == [] and d.account_nav == []
