"""Portfolio: definizione (sleeve + schema pesi) con faccia di backtest.
La faccia live è in runner.py."""
from __future__ import annotations

from dataclasses import dataclass, field

import pandas as pd

from src.portfolio import weighting as W
from src.portfolio.sleeves import all_sleeve_equities, sleeve_returns_df
from scripts.analysis.combine_portfolio import port_returns, metrics, yearly_returns, SPLIT


@dataclass
class SleeveSpec:
    kind: str
    name: str
    sid: str
    asset: str | None = None
    a: str | None = None
    b: str | None = None
    tf: str = "1h"
    params: dict = field(default_factory=dict)
    cluster: str = ""


@dataclass
class PortfolioResult:
    code: str
    weights: dict
    full: dict
    oos: dict
    yearly: dict
    risk: dict


@dataclass
class Portfolio:
    code: str
    label: str
    sleeves: list[SleeveSpec]
    weighting: str = "equal"
    weights: dict | None = None
    caps: dict | None = None
    total_capital: float = 1000.0
    leverage: float = 3.0
    rebalance: str = "1D"
    vol_lookback: int = 90

    @property
    def sleeve_ids(self) -> list[str]:
        return [s.sid for s in self.sleeves]

    @property
    def clusters(self) -> dict[str, str]:
        return {s.sid: (s.cluster or s.sid) for s in self.sleeves}

    def weight_vector(self, returns_df: pd.DataFrame | None = None) -> dict[str, float]:
        return W.weight_vector(
            self.weighting, self.sleeve_ids, returns_df,
            weights=self.weights, caps=self.caps,
            clusters=self.clusters, lookback=self.vol_lookback,
        )

    def backtest(self) -> PortfolioResult:
        eq = all_sleeve_equities()
        members = {sid: eq[sid] for sid in self.sleeve_ids}
        dr = sleeve_returns_df(self.sleeve_ids)
        w = self.weight_vector(dr)
        port_dr = port_returns(members, w)
        full, oos = metrics(port_dr), metrics(port_dr, lo=SPLIT)
        import numpy as np
        we = np.ones(len(self.sleeve_ids)) / len(self.sleeve_ids)
        cov = dr.cov().values
        pv = float(we @ cov @ we)
        rc = we * (cov @ we)
        risk = {sid: float(rc[k] / pv * 100) if pv > 0 else 0.0
                for k, sid in enumerate(self.sleeve_ids)}
        return PortfolioResult(self.code, w, full, oos, yearly_returns(port_dr), risk)


def load_active_portfolio(config_path) -> "Portfolio":
    """Carica il portafoglio attivo da portfolios.yml applicando gli override."""
    import yaml
    from pathlib import Path
    from scripts.portfolios._defs import PORTFOLIOS

    cfg = yaml.safe_load(Path(config_path).read_text())
    p = PORTFOLIOS[cfg["active"]]
    ov = cfg.get("overrides", {})
    for k in ("total_capital", "weighting", "caps", "leverage", "rebalance", "vol_lookback"):
        if k in ov and ov[k] is not None:
            setattr(p, k, ov[k])
    return p
