"""Per-symbol target WEIGHTS per day for the 4 Bybit edges — the seam that turns the RETURNS book into a
LIVE per-symbol paper book WITHOUT changing any edge's computed return.

Each Bybit edge already books a per-SLEEVE daily return whose value is, internally, `Σ_symbol weight·return`
(with each edge's own per-symbol return convention). This module surfaces those per-symbol `(weight, return)`
pairs per day by reusing the EXACT signal/sizing logic of each runner (a `weights_and_contributions()`
method added to each engine), so:

    HARD RECONCILIATION INVARIANT (proved by test_bybit_paper_reconciliation):
        for every sleeve and day,  Σ_symbol weight·return  ==  sleeve_returns_from_store(.., cost_bps=0.0)

i.e. the extracted weights reproduce each sleeve's GROSS (cost-free) return engine exactly — the live book
and the returns book are the SAME book, no edge drift. The per-turnover trading cost is NOT baked into the
weights; it is applied identically downstream (the live book's `trading_cost` at the Bybit 5.5bp rate, the
same haircut the stored `bybit_sleeve_ret` charges), so the cost-free invariant is the clean one to assert.

Per-symbol RETURN CONVENTION by sleeve (the second element of each pair) — matches what the engine books:
  * crypto_tstrend — close-to-close price return (price-MTM in the live book);
  * positioning    — close-to-close next-day price return (price-MTM);
  * unlock         — `short_leg_pnl` (short alt + long-BTC hedge + funding + squeeze stop; the BTC/funding
                     legs are book-level, so the live book marks the per-symbol lot to price, exactly like
                     the legacy venue's unlock sleeve's live view);
  * xsfunding      — full daily carry return `(spot_ret − perp_ret) + funding` (the carry convention; the
                     live book books this sleeve as `return_mode == "carry"`, like the legacy venue's xsfunding).

READ-ONLY + memory-bounded: reuses the same `_UniverseRestrictedStore` seam the runners use (only the liquid
`universe` symbols load). Writes NOTHING. The `xvenue_carry` edge is NOT a book sleeve and is excluded.
"""
from __future__ import annotations

from typing import Any

from fxhnt.application.bybit_book_eval import (
    _DEFAULT_BYBIT_SLEEVES,
    _SLEEVE_TO_EDGE,
    _bounded_store,
)

# Per-symbol return convention each sleeve books — the live book consumes this to decide price-MTM vs carry.
PRICE_SLEEVES = frozenset({"crypto_tstrend", "unlock", "positioning"})
CARRY_SLEEVES = frozenset({"xsfunding"})


def sleeve_weights_and_contributions(store: Any, sleeve: str, *, universe: set[str] | None = None,
                                     unlock_events: list[dict[str, Any]] | None = None,
                                     positioning_coin_gross_cap: float | None = None,
                                     ) -> dict[int, dict[str, tuple[float, float]]]:
    """`{epoch_day: {symbol: (weight, symbol_return)}}` for ONE Bybit edge, reusing the edge's EXACT engine.

    The sum `Σ_symbol weight·symbol_return` over a day equals that sleeve's GROSS (cost_bps=0) daily return
    from `sleeve_returns_from_store` — the reconciliation invariant. `universe` is the memory bound (liquid
    subset). `unlock_events` is required by the unlock sleeve (absent → empty, the edge is uncomputable).
    `positioning_coin_gross_cap` (positioning sleeve ONLY; ignored by the others) must be passed the SAME
    value used to compute the positioning sleeve's stored return, or the reconciliation invariant breaks —
    the extracted weights would be uncapped while the stored return is capped. Default None (off) preserves
    the byte-identical uncapped behaviour. READ-ONLY; writes nothing. `sleeve` accepts the sleeve or edge name."""
    edge = _SLEEVE_TO_EDGE.get(sleeve, sleeve)
    if edge == "tstrend":
        from fxhnt.application.trend_runner import TrendRunner

        bounded = _bounded_store(store, universe)
        panel = bounded.crypto_close_panel()
        if not panel:
            return {}
        # funding-aware: the per-symbol contribution nets the perp funding each long pays, matching the
        # funding-inclusive sleeve return so the reconciliation invariant holds.
        return TrendRunner(panel, long_short=False,
                           funding=bounded.crypto_funding_panel()).weights_and_contributions()
    if edge == "unlock":
        from fxhnt.application.unlock_runner import UnlockShortRunner, warehouse_unlock_panel

        if not unlock_events:
            return {}
        bounded = _bounded_store(store, universe)
        panel = bounded.crypto_close_panel()
        if not panel:
            return {}
        return UnlockShortRunner(warehouse_unlock_panel(panel), unlock_events).weights_and_contributions()
    if edge == "xsfunding":
        from fxhnt.application.bybit_carry_revalidate import carry_weights_and_contributions

        # The carry pipeline restricts at the panel level itself, so pass the RAW store + universe.
        return carry_weights_and_contributions(store, universe=universe)
    if edge == "positioning":
        from fxhnt.application.bybit_positioning_eval import positioning_weights_and_contributions

        # positioning does its OWN universe wrap (it also reads the long_ratio panel) — pass the raw store.
        return positioning_weights_and_contributions(store, universe=universe,
                                                      coin_gross_cap=positioning_coin_gross_cap)
    raise ValueError(f"unknown sleeve {sleeve!r} (known book sleeves: {_DEFAULT_BYBIT_SLEEVES})")


def reconciliation_series(weights_by_day: dict[int, dict[str, tuple[float, float]]]) -> dict[int, float]:
    """Collapse `{day: {symbol: (w, r)}}` into `{day: Σ_symbol w·r}` — the GROSS book return implied by the
    extracted weights. The reconciliation test asserts this equals `sleeve_returns_from_store(cost_bps=0)`."""
    return {day: sum(w * r for (w, r) in row.values()) for day, row in weights_by_day.items()}
