"""Map each DEPLOY strategy to its honest-reference return series (for ex-ante sizing) + its capacity
ceiling — the integration seam between the honest-reference engines (spec 0-9) and the strategy-level
allocation engine (`compute_allocation` / `record_allocation`).

PURE + network-free: `store` is read-only (panel reads via the existing edge runners); NOTHING is written.
Everything is composed from the existing engines — no return math is reimplemented here:
  * `sleeve_weights_and_contributions` (per-symbol weights) + `honest_sleeve_returns` (capacity + cost) build
    each sleeve's honest daily series at a given AUM;
  * `risk_parity_weights` + `book_returns` aggregate the crypto BOOK (`_book_curve` below) — the crypto
    deploy target (reverted 2026-07-16, see below);
  * `sharpe_of` + `capacity_ceiling_from_curve` turn an AUM→Sharpe curve into the honest capacity ceiling.

LOAD-ONCE / REPRICE (2026-07-16, `2026-07-16-honest-inputs-load-once-reprice.md` Task 1): each sleeve's
AUM-INDEPENDENT heavy inputs (weights + turnover panel) are loaded ONCE via `_sleeve_inputs`, then repriced
per AUM by `honest_sleeve_returns`/`capacity_curve` (cheap) — NOT reloaded per AUM. This mirrors the
honest-reference REPORT path (`honest_report.py`), which already does load-once/reprice.

The deploy→honest mapping (crypto-only today; reverted 2026-07-16, plan
`2026-07-16-book-representation-and-venue-cap.md` Task A, back to the BOOK as the deploy target — an
in-cluster measurement showed the intervening per-sleeve allocation collapses to `positioning` alone (~18%
deployed) while the diversified book deploys 75% at lower risk, funding-stress-checked):
  * "bybit_4edge"  → the risk-parity BOOK series at the AUM (via `_book_curve`, aggregating
                     `_BOOK_SLEEVES`); ceiling from its capacity curve. This IS the crypto allocation target.
  * "positioning" / "xsfunding" / "unlock" (the rest of `_CRYPTO_DEPLOY_SIDS`) → that sleeve's OWN series at
                     the AUM (via `_deploy_curve`); ceiling from its capacity curve. Available if any sleeve
                     is ever deployed standalone — the registry keeps them `tier: research` with no
                     `promote_on_pass`, so they do NOT enter the deploy set on their own (see `registry.py`).
  * any OTHER sid  → the equity default ceiling, NO series (the caller falls back to its own reference for
                     equity strategies; we do NOT fabricate a crypto series for equity).

CRITICAL — the key-type seam. The honest series are `dict[int, float]` (epoch-DAY int keys, `ts // 86400`),
but the downstream consumers (`compute_allocation` / `full_history_vol` / `deploy_book_returns` via
`record_allocation`) key on ISO DATE STRINGS (e.g. "2026-06-01") and cross-align strategies BY KEY — and the
nav fallback in `record_allocation` keys on those SAME ISO date strings. So every series is returned with the
epoch-day int converted to the SAME ISO date string via `_iso` (the canonical `date(1970,1,1)+timedelta(days)`
conversion) for every strategy, so `deploy_book_returns` aligns positioning, xsfunding, unlock, AND any
nav-fallback series on identical calendar-day keys (a test asserts positioning & xsfunding share a common
key)."""
from __future__ import annotations

import datetime as _dt
from typing import Any

from fxhnt.application.allocation_engine import capacity_ceiling_from_curve
from fxhnt.application.bybit_paper_weights import sleeve_weights_and_contributions
from fxhnt.application.honest_gates import book_returns, risk_parity_weights, sharpe_of
from fxhnt.application.honest_reference import capacity_curve, honest_sleeve_returns

# The crypto sleeves the `bybit_4edge` deploy strategy books, EXCLUDING `tstrend` (it has no standalone store
# series — the honest-reference book is the carry/unlock/positioning trio). `unlock` is dropped downstream
# when its event calendar is absent (empty series). Ordered; the book aggregation is order-independent.
_BOOK_SLEEVES = ("xsfunding", "unlock", "positioning")

# The deploy strategies with a crypto honest-reference series — `bybit_4edge` (the risk-parity BOOK, the
# deploy target again since 2026-07-16 Task A) plus its three constituent sleeves (available if ever
# deployed standalone). Any sid NOT here gets the equity default ceiling and no series (the caller supplies
# the equity reference).
_CRYPTO_DEPLOY_SIDS = frozenset({"positioning", "xsfunding", "unlock", "bybit_4edge"})

_EPOCH = _dt.date(1970, 1, 1)


def _iso(epoch_day: int) -> str:
    """Epoch-DAY int (`ts // 86400`) -> the ISO date string the forward-nav record uses, so honest and nav
    series cross-align on the same calendar day in `deploy_book_returns`. Same conversion as the edge engines'
    `_epoch_day_to_iso` (base = 1970-01-01)."""
    return (_EPOCH + _dt.timedelta(days=int(epoch_day))).isoformat()


def _sleeve_inputs(store: Any, sleeve: str,
                   unlock_events: list[dict[str, Any]] | None,
                   positioning_coin_gross_cap: float | None = None) -> tuple[dict, dict] | None:
    """AUM-INDEPENDENT heavy inputs for a sleeve: (weights_by_day, turnover_panel). Load ONCE, reprice across
    AUMs via honest_sleeve_returns/capacity_curve — the turnover panel read is the memory cost, so it must NOT
    be reloaded per AUM. None when the sleeve is uncomputable (e.g. unlock with no events).
    `positioning_coin_gross_cap` (positioning sleeve ONLY) forwards into `sleeve_weights_and_contributions` —
    default None preserves the byte-identical uncapped behaviour. READ-ONLY."""
    wbd = sleeve_weights_and_contributions(store, sleeve, unlock_events=unlock_events,
                                           positioning_coin_gross_cap=positioning_coin_gross_cap)
    if not wbd:
        return None
    symbols = sorted({s for row in wbd.values() for s in row})
    turnover = store.read_panel(symbols, "turnover")
    return wbd, turnover


def _book_curve(store: Any, spread_bps: dict[str, float], unlock_events: list[dict[str, Any]] | None,
                aums, *, participation_cap: float, impact_k: float,
                positioning_coin_gross_cap: float | None = None) -> dict[float, dict[int, float]]:
    """{aum: risk-parity BOOK series} across `aums`, loading each `_BOOK_SLEEVES` sleeve's inputs ONCE then
    repricing per AUM. Empty sleeves (uncomputable) are dropped; an AUM with no computable sleeve -> {}.
    `positioning_coin_gross_cap` forwards into `_sleeve_inputs` (positioning sleeve ONLY)."""
    loaded = {sleeve: inp for sleeve in _BOOK_SLEEVES
              if (inp := _sleeve_inputs(store, sleeve, unlock_events,
                                        positioning_coin_gross_cap=positioning_coin_gross_cap)) is not None}
    out: dict[float, dict[int, float]] = {}
    for aum in aums:
        per: dict[str, dict[int, float]] = {}
        for sleeve, (wbd, turnover) in loaded.items():
            series = honest_sleeve_returns(wbd, turnover, spread_bps, aum=aum,
                                           participation_cap=participation_cap, impact_k=impact_k)
            if series:
                per[sleeve] = series
        out[aum] = book_returns(per, risk_parity_weights(per)) if per else {}
    return out


def _deploy_curve(store: Any, sid: str, spread_bps: dict[str, float],
                  unlock_events: list[dict[str, Any]] | None, aums, *,
                  participation_cap: float, impact_k: float,
                  positioning_coin_gross_cap: float | None = None) -> dict[float, dict[int, float]]:
    """{aum: honest series} for a crypto deploy `sid` across `aums`, loading AUM-independent inputs ONCE.
    `bybit_4edge` -> the risk-parity book; a sleeve sid -> its own series (reuses `capacity_curve`); other -> {}.
    `positioning_coin_gross_cap` forwards through (positioning sleeve ONLY)."""
    if sid == "bybit_4edge":
        return _book_curve(store, spread_bps, unlock_events, aums,
                           participation_cap=participation_cap, impact_k=impact_k,
                           positioning_coin_gross_cap=positioning_coin_gross_cap)
    if sid in _CRYPTO_DEPLOY_SIDS:
        inputs = _sleeve_inputs(store, sid, unlock_events,
                                positioning_coin_gross_cap=positioning_coin_gross_cap)
        if inputs is None:
            return {aum: {} for aum in aums}
        wbd, turnover = inputs
        return capacity_curve(wbd, turnover, spread_bps, aums=aums,
                              participation_cap=participation_cap, impact_k=impact_k)
    return {aum: {} for aum in aums}


def honest_allocation_inputs(
    store: Any, spread_bps: dict[str, float], unlock_events: list[dict[str, Any]] | None,
    *, deploy_sids, target_aum: float, aums, participation_cap: float = 0.03, impact_k: float = 1.0,
    min_sharpe: float = 1.0, equity_default_ceiling: float = 10_000_000.0,
    positioning_coin_gross_cap: float | None = None,
) -> tuple[dict[str, dict[str, float]], dict[str, float | None]]:
    """`(returns_by_strategy, capacity_ceiling_by_strategy)` for the deploy set — the allocation engine's
    honest inputs.

    For each crypto deploy sid: its honest series at `target_aum` (returned STRING-keyed, see the module
    docstring seam) plus a capacity ceiling = `capacity_ceiling_from_curve({aum: sharpe_of(series(aum))
    for aum in aums}, min_sharpe)`. Non-crypto sids get `equity_default_ceiling` and NO series (the caller
    supplies their reference). `positioning_coin_gross_cap` (positioning sleeve ONLY) forwards into
    `_deploy_curve` / `_sleeve_inputs` so the honest-reference basis the LIVE allocation engine
    (`record_allocation`) sizes against honors the same per-coin cap the live capped return uses — default
    None preserves the byte-identical uncapped behaviour. PURE + read-only."""
    returns_by_strategy: dict[str, dict[str, float]] = {}
    ceilings: dict[str, float | None] = {}
    for sid in deploy_sids:
        if sid not in _CRYPTO_DEPLOY_SIDS:
            ceilings[sid] = equity_default_ceiling      # equity / non-crypto — caller falls back; no series
            continue
        # AUM-independent panels loaded ONCE inside _deploy_curve; base + curve are repriced from them.
        all_aums = sorted({float(target_aum), *(float(a) for a in aums)})
        curve_series = _deploy_curve(store, sid, spread_bps, unlock_events, all_aums,
                                     participation_cap=participation_cap, impact_k=impact_k,
                                     positioning_coin_gross_cap=positioning_coin_gross_cap)
        base = curve_series.get(float(target_aum), {})
        if base:
            # int epoch-day -> ISO date string, the SAME key the nav fallback uses, consistently across every
            # strategy, so deploy_book_returns aligns honest and nav series on the same calendar day.
            returns_by_strategy[sid] = {_iso(day): ret for day, ret in base.items()}
        curve = {aum: sharpe_of(curve_series.get(float(aum), {})) for aum in aums}
        ceilings[sid] = capacity_ceiling_from_curve(curve, min_sharpe)
    return returns_by_strategy, ceilings
