"""Sub-project C2 — record the EXECUTED Bybit crypto track and bridge B2a/B3's dollar allocation to the venue.

`crypto_envelope` = the B2a (B3-funding-filtered) dollars for `bybit_4edge`, capped at the testnet equity
(absent/de-funded -> 0 -> flatten). `compute_crypto_exec_return` measures the executed book's daily return on
the ENVELOPE (not the full testnet equity), as the P&L of the HELD positions since the last rebalance PLUS
realized funding MINUS fees (funding is edge return; a re-allocation is a capital flow, not a gain). Mirrors
C1's multistrat_exec_record, extended for crypto funding/fees. No new execution engine, no new return store."""
from __future__ import annotations

import datetime as dt
from typing import Any


def crypto_envelope(repo: Any, equity: float) -> float:
    return min(float(repo.current_allocation().get("bybit_4edge", 0.0)), float(equity))


def paper_or_b2a_crypto_envelope(repo: Any, equity: float, paper_envelope: float, is_paper: bool) -> float:
    """PAPER-validation envelope (min(paper, equity)) when `paper_envelope > 0`, bypassing B2a — refused on a
    non-testnet account (never widen real-capital exposure). Otherwise the B2a/B3-gated `crypto_envelope`."""
    if paper_envelope > 0.0:
        if not is_paper:
            raise ValueError("paper-envelope refused: bybit account is not testnet/paper")
        return min(float(paper_envelope), float(equity))
    return crypto_envelope(repo, equity)


def build_crypto_pos_state(positions: dict[str, float], marks: dict[str, float], envelope: float,
                           funding_cursor_ts: int, venue: str = "") -> dict[str, Any]:
    return {"positions": {s: float(q) for s, q in positions.items()},
            "marks": {s: float(m) for s, m in marks.items()},
            "envelope": float(envelope), "funding_cursor_ts": int(funding_cursor_ts),
            "venue": str(venue)}


def compute_crypto_exec_return(pos_prior: dict[str, Any], today_marks: dict[str, float],
                               funding: float, fees: float) -> float | None:
    """Envelope-basis, flow-excluded, funding/fee-net executed return:
        (price_pnl + funding - fees) / envelope_prior
    where price_pnl marks the PRIOR held positions (prior marks -> today marks). None on inception / flat.
    A symbol missing from PRIOR marks (rotated out of the universe then back in) yields 0 P&L for that symbol
    THIS period (falls back to today's mark on both sides of the price_pnl diff), never a phantom entry-at-
    price-0 that would book its entire notional as a spurious gain."""
    if not pos_prior:
        return None                                              # first run / no prior book -> inception
    envelope = float(pos_prior.get("envelope", 0.0))
    if envelope <= 0.0:
        # flat (de-funded/killswitched) -> no return. NOTE: re-entry-day rebalance fees are not booked here
        # (inception/flat has no envelope_prior basis to net them against) — an accepted small, bounded
        # understatement; precise re-entry-cost accounting is deferred to C3 (real-money accounting).
        return None
    positions: dict[str, float] = pos_prior.get("positions", {})
    prior_marks: dict[str, float] = pos_prior.get("marks", {})
    val_prior = sum(q * prior_marks.get(s, today_marks.get(s, 0.0)) for s, q in positions.items())
    val_today = sum(q * today_marks.get(s, prior_marks.get(s, 0.0)) for s, q in positions.items())
    price_pnl = val_today - val_prior
    return (price_pnl + float(funding) - float(fees)) / envelope


def apply_fills(current: dict[str, float], fills: list[dict[str, Any]]) -> dict[str, float]:
    """The post-rebalance book = `current` signed base qty updated by each SUCCESSFUL fill's signed filled qty
    (BUY > 0, SELL < 0). Error rows are ignored; near-zero residual positions are dropped. This is the real
    placed book (reflects snapping/partial fills) — the next run's return basis."""
    out = dict(current)
    for f in fills:
        if f.get("error"):
            continue
        side = str(f["side"]).upper()
        if side not in ("BUY", "SELL"):
            raise ValueError(f"apply_fills: unexpected fill side {f['side']!r}")
        signed = float(f["qty"]) * (1.0 if side == "BUY" else -1.0)
        out[f["symbol"]] = out.get(f["symbol"], 0.0) + signed
    return {s: q for s, q in out.items() if abs(q) > 1e-12}


class _CryptoExecBookingStrategy:
    """Trivial observed ForwardStrategy for run_track: books today's pre-computed executed return (or nothing
    on inception). The return is computed by the record asset (it has the marks/funding/fees)."""
    def __init__(self, exec_return: float | None) -> None:
        self._r = exec_return

    def advance(self, last_date: str | None, extra: dict[str, Any]) -> tuple[list[tuple[str, float]], dict[str, Any]]:
        if self._r is None:
            return [], extra
        return [(str(extra.get("_today")), float(self._r))], extra


def record_bybit_exec_track(repo: Any, exec_return: float | None, new_positions: dict[str, float],
                            today_marks: dict[str, float], envelope: float, funding_cursor_ts: int,
                            today: str, at: dt.datetime, venue: str = "") -> None:
    """Book `exec_return` as an observed forward_record for `bybit_4edge_exec` via A's run_track (anchor +
    record + nav/summary), then carry the next run's basis (positions/marks/envelope/funding cursor) under the
    book-state key `bybit_4edge_exec_pos` (SEPARATE from run_track's own book_state). `venue` is recorded on
    the exec book-state."""
    from fxhnt.application.forward_engine import run_track
    run_track(repo, "bybit_4edge_exec", _CryptoExecBookingStrategy(exec_return), today=today, at=at)
    repo.set_book_state("bybit_4edge_exec_pos",
                        build_crypto_pos_state(new_positions, today_marks, envelope, funding_cursor_ts, venue),
                        at=at)
