"""D2.1 — capture the REAL DU-paper-account state produced by an `execute-multistrat` run: the fills that
actually got placed (persisted into the existing `exec_fills` table, now strategy-tagged) and the
post-rebalance account snapshot (`ibkr_account_nav`). This is ADDITIVE to the existing intended-book return
basis (`multistrat_exec_pos`/`forward_record` via `record_exec_track`, unchanged) — a data-layer record of
what the real account actually did, not the gate-relevant return basis.

BEST-EFFORT throughout: a persistence failure logs and never raises, so a capture bug can never fail or
block an otherwise-successful rebalance (mirrors the existing best-effort ref-persist pattern elsewhere in
the codebase)."""
from __future__ import annotations

import datetime as dt
import logging
from collections.abc import Iterable
from dataclasses import dataclass
from typing import Any

log = logging.getLogger(__name__)


@dataclass(frozen=True)
class FillDTO:
    symbol: str
    side: str
    qty: float
    price: float
    fee: float
    status: str
    shortfall_bps: float


def _intended_price(trade: Any) -> float | None:
    """The order's limit price, or None when there is no real one to compare a fill against. ib-async's
    `Order.lmtPrice` defaults to the IBAPI sentinel `UNSET_DOUBLE` (== 1.7976931348623157e+308, TRUTHY —
    not None), which every `MarketOrder` carries; treating that as a real intended price fabricates ~−10000
    bps of slippage on every market fill. So a missing (None) OR sentinel value → None (honest 0.0
    shortfall). A real limit/combo order carries a genuine finite lmtPrice → used."""
    from ib_async.util import UNSET_DOUBLE

    raw = getattr(getattr(trade, "order", None), "lmtPrice", None)
    if raw is None or raw == UNSET_DOUBLE:
        return None
    return float(raw)


def capture_fills(trades: Iterable[Any]) -> list[FillDTO]:
    """Map ib-async `Trade` objects (each carrying `.orderStatus`, `.order`, and `.fills` — one per partial/
    full execution) to `FillDTO`s. `shortfall_bps` = (fill price − intended price) / intended price · 1e4,
    where the intended price is the order's limit price (`order.lmtPrice`) when a real one was set; whole-
    share market orders (the current multistrat execution path) carry the `UNSET_DOUBLE` sentinel, treated as
    "no intended price" so `shortfall_bps` is 0.0 for them (see `_intended_price`) — the real slippage input
    is forward-looking for any future limit/combo order path."""
    out: list[FillDTO] = []
    for trade in trades:
        status = str(getattr(getattr(trade, "orderStatus", None), "status", ""))
        intended_price = _intended_price(trade)
        for fill in getattr(trade, "fills", []):
            execution = fill.execution
            price = float(execution.price)
            report = getattr(fill, "commissionReport", None)
            fee = float(getattr(report, "commission", 0.0)) if report is not None else 0.0
            shortfall_bps = ((price - intended_price) / intended_price) * 1e4 if intended_price else 0.0
            out.append(FillDTO(symbol=fill.contract.symbol, side=execution.side, qty=float(execution.shares),
                               price=price, fee=fee, status=status, shortfall_bps=shortfall_bps))
    return out


def persist_execution(repo: Any, *, strategy_id: str, rebalance_id: str, fills: list[FillDTO],
                      snapshot: Any, at: dt.datetime) -> None:
    """Best-effort write of `fills` (tagged `strategy_id`, into `exec_fills`) and `snapshot` (into
    `ibkr_account_nav`, keyed by `at`'s date). The two writes are independently guarded — a failure in one
    never blocks the other, and NEITHER ever raises: a persistence bug here must never fail an already-placed
    rebalance."""
    try:
        repo.record_fills(strategy_id=strategy_id, rebalance_id=rebalance_id, fills=fills, at=at)
    except Exception:
        log.warning("exec capture: failed to persist fills for %s (best-effort, non-blocking)",
                    strategy_id, exc_info=True)
    try:
        repo.replace_snapshot(at.date().isoformat(), nlv=snapshot.nlv, cash=snapshot.cash,
                              gross=snapshot.gross, positions=snapshot.positions, at=at)
    except Exception:
        log.warning("exec capture: failed to persist account snapshot for %s (best-effort, non-blocking)",
                    strategy_id, exc_info=True)
