"""Portfolio-level validation — judge a BOOK (a diversified combination of sleeves) through the gauntlet,
not its individual sleeves. A book of marginal-but-real, uncorrelated edges can be robustly significant
where no single sleeve is — the unit of alpha is the book (the lesson the whole research arc taught).

n_book_trials must reflect how many book RECIPES were tried: 1 for a pre-specified diversified book
(e.g. 'trend on all futures, equal risk' — a theory-motivated hypothesis, not data-mined). Pass the
real count if you searched many books, so the deflation stays honest.
"""
from __future__ import annotations

from pydantic import BaseModel

from fxhnt.config import Settings
from fxhnt.domain.backtest import compute_stats, run_backtest
from fxhnt.domain.gauntlet import evaluate
from fxhnt.domain.models import BacktestStats, Verdict
from fxhnt.domain.portfolio import Book, combine_returns
from fxhnt.ports.data import DataProvider
from fxhnt.ports.repository import AnalyticalStore

_MIN_HISTORY = 300


class BookEvaluation(BaseModel):
    book: str
    stats: BacktestStats
    verdict: Verdict
    n_obs: int
    is_returns: list[float] = []   # split halves, kept so a caller can re-deflate over its OWN full search
    oos_returns: list[float] = []


class PortfolioEvaluationService:
    def __init__(self, data: DataProvider, settings: Settings, analytical: AnalyticalStore | None = None) -> None:
        self._data = data
        self._s = settings
        self._an = analytical

    def _prices(self, market):  # type: ignore[no-untyped-def]
        if self._an is not None:
            cached = self._an.load_prices(market)
            if cached is not None and len(cached) >= _MIN_HISTORY:
                return cached
        prices = self._data.fetch(market)
        if self._an is not None:
            self._an.save_prices(prices)
        return prices

    def evaluate_book(self, book: Book, *, n_book_trials: int = 1) -> BookEvaluation:
        sleeves = []
        for alloc in book.allocations:
            prices = self._prices(alloc.market)
            bt = run_backtest(prices, alloc.spec, self._s.cost_bps_per_turnover)
            sleeves.append((alloc.weight, prices.dates, bt.returns))

        _, book_returns = combine_returns(sleeves)
        stats = compute_stats(book_returns)
        g = self._s.gauntlet
        split = int((1.0 - g.oos_fraction) * len(book_returns))
        verdict = evaluate(
            book_returns[:split], book_returns[split:], n_trials=n_book_trials, sr_variance=0.0,
            dsr_min=g.dsr_min, oos_min_sharpe=g.oos_min_sharpe, max_is_oos_decay=g.max_is_oos_decay,
            has_economic_rationale=True,  # a diversified premium-harvest book is structurally justified
        )
        return BookEvaluation(book=book.name, stats=stats, verdict=verdict, n_obs=len(book_returns),
                              is_returns=book_returns[:split].tolist(), oos_returns=book_returns[split:].tolist())
