"""The combined book — the deployable culmination of the research program. Two validated, uncorrelated
edges, Sharpe-weighted and vol-targeted:

  * crypto cross-sectional momentum  — the ALPHA engine (Sharpe ~1.4, DSR 0.98, the real edge);
  * futures diversified trend         — the uncorrelated DIVERSIFIER / crisis-hedge (Sharpe ~0.3, corr +0.03).

The crypto sleeve dominates the Sharpe; the trend sleeve earns its place by being uncorrelated (structural
diversification, not Sharpe-boost). Every cent costed; honest IS/OOS. This is what the whole arc found —
nine rejected fantasies, one real edge plus one hedge.
"""
from __future__ import annotations

import datetime as dt
from dataclasses import dataclass

import numpy as np

from fxhnt.application.combined_book_source import CombinedBookSource, RawFileSource
from fxhnt.application.crypto_momentum_research import xs_momentum_book
from fxhnt.application.tsmom_research import _book_returns, vol_targeted_trend
from fxhnt.domain.gauntlet import per_period_sharpe

_ANN = np.sqrt(365.0)


def _ann_sharpe(x: np.ndarray) -> float:
    b = x[np.isfinite(x) & (x != 0)]
    return float(b.mean() / b.std() * _ANN) if b.std() > 0 else 0.0


@dataclass
class CombinedBookResult:
    overlap_days: int
    start: str
    end: str
    crypto_sharpe: float
    trend_sharpe: float
    correlation: float
    crypto_weight: float
    combined_sharpe: float
    is_sharpe: float
    oos_sharpe: float
    max_drawdown: float
    target_vol: float


class CombinedBook:
    def __init__(self, data_dir: str, futures: list[str], *, source: CombinedBookSource | None = None,
                 crypto_subdir: str = "crypto_pit", crypto_lookback: int = 30, crypto_cost_bps: float = 8.0,
                 futures_cost_bps: float = 1.0, target_vol: float = 0.15) -> None:
        self._futures = futures
        self._source = source if source is not None else RawFileSource(data_dir, crypto_subdir)
        self._cl, self._cc, self._fc, self._tv = crypto_lookback, crypto_cost_bps, futures_cost_bps, target_vol

    def _crypto(self) -> dict[str, float]:
        days, _, close, ret = self._source.crypto_panel()
        book = xs_momentum_book(close, ret, self._cl, cost_bps=self._cc)
        keys = [dt.datetime.fromtimestamp(int(d) * 86400, dt.timezone.utc).strftime("%Y-%m-%d") for d in days]
        return dict(zip(keys, book))

    def _trend(self) -> dict[str, float]:
        sleeves = []
        for s in self._futures:
            try:
                ps = self._source.futures_series(s)
            except Exception:
                continue
            sleeves.append(vol_targeted_trend(ps, (20, 60, 120, 250), cost_bps=self._fc))
        dates, book = _book_returns(sleeves)
        return dict(zip(dates, book))

    def build(self) -> tuple[CombinedBookResult, list[str], np.ndarray]:
        cmap, tmap = self._crypto(), self._trend()
        if not tmap:
            # Crypto-only: no futures configured — use all crypto dates directly.
            common = sorted(cmap)
            cv = np.array([cmap[d] for d in common])
            tv = np.zeros_like(cv)
            sc, st, wc = _ann_sharpe(cv), 0.0, 1.0
            combo = (cv / cv.std() if cv.std() > 0 else cv) / _ANN * self._tv
        else:
            common = sorted(set(cmap) & set(tmap))
            cv = np.array([cmap[d] for d in common])
            tv = np.array([tmap[d] for d in common])
            sc, st = _ann_sharpe(cv), _ann_sharpe(tv)
            wc = sc / (sc + st) if (sc + st) > 0 else 0.5
            # unit-vol sleeves, Sharpe-weighted, then scale the whole book to target_vol
            combo = wc * (cv / cv.std()) + (1 - wc) * (tv / tv.std())
            combo = combo / (combo.std() * _ANN) * self._tv
        split = int(0.6 * len(combo))
        fin = combo[np.isfinite(combo)]
        eq = np.cumprod(1.0 + fin) if len(fin) else np.array([1.0])
        mdd = float((eq / np.maximum.accumulate(eq) - 1.0).min())
        corr = float(np.corrcoef(cv, tv)[0, 1]) if tmap else 0.0
        res = CombinedBookResult(
            overlap_days=len(common), start=common[0] if common else "", end=common[-1] if common else "",
            crypto_sharpe=sc, trend_sharpe=st, correlation=corr, crypto_weight=wc,
            combined_sharpe=_ann_sharpe(combo),
            is_sharpe=float(per_period_sharpe(combo[:split]) * _ANN),
            oos_sharpe=float(per_period_sharpe(combo[split:]) * _ANN), max_drawdown=mdd, target_vol=self._tv)
        return res, common, combo
