"""
Regime-router backtest: one position at a time, action driven by daily state classification.
"""

from __future__ import annotations

import math
from typing import Any, Callable

import pandas as pd

from RenTech.core.options_data_loader import OptionChain
from RenTech.strategy_stack import research_literature_theta_strategies as L
from RenTech.strategy_stack.spx_regime_state_space.classifier import RegimeState, classify_regime
from RenTech.strategy_stack.spx_regime_state_space.features import FeatureState
from RenTech.strategy_stack.spx_regime_state_space.trades import (
    ACTION_TEMPLATES,
    TradeTemplate,
    can_build_trade,
    trade_fn_for_kind,
)

_norm = L._norm


def _feature_tuple_from_row(row: pd.Series) -> FeatureState:
    return (
        row["vrp_state"],
        row["hurst_state"],
        row["skew_state"],
        row["term_state"],
        row["spot_vol_state"],
        row["ma_dist_state"],
    )


def run_regime_router(
    days: list[pd.Timestamp],
    features: pd.DataFrame,
    get_chain: Callable[[pd.Timestamp], OptionChain],
    *,
    hold: int = 5,
) -> tuple[list[dict[str, Any]], pd.DataFrame]:
    """
    Always-on regime router: enter when action != cash and flat; hold ``hold`` sessions.
    """
    trades: list[dict[str, Any]] = []
    daily_rows: list[dict[str, Any]] = []
    pending: tuple[int, TradeTemplate, RegimeState, float] | None = None

    for i, d in enumerate(days):
        dt = _norm(d)
        row = features.reindex([dt]).iloc[0]
        st = classify_regime(_feature_tuple_from_row(row))
        ch = get_chain(d)
        spy_c = float(row["close"])

        daily_rows.append(
            {
                "date": dt.strftime("%Y-%m-%d"),
                "close": spy_c,
                "vrp30": float(row["vrp30"]) if pd.notna(row.get("vrp30")) else None,
                "hurst": float(row["hurst"]) if pd.notna(row.get("hurst")) else None,
                "skew25": float(row["skew25"]) if pd.notna(row.get("skew25")) else None,
                "term_ratio": float(row["term_ratio"]) if pd.notna(row.get("term_ratio")) else None,
                "spot_vol_corr": float(row["spot_vol_corr"]) if pd.notna(row.get("spot_vol_corr")) else None,
                "ma_dist": float(row["ma_dist"]) if pd.notna(row.get("ma_dist")) else None,
                "vrp_state": st.feature_state[0],
                "hurst_state": st.feature_state[1],
                "skew_state": st.feature_state[2],
                "term_state": st.feature_state[3],
                "spot_vol_state": st.feature_state[4],
                "ma_dist_state": st.feature_state[5],
                "regime": st.regime,
                "action": st.action,
                "rationale": st.rationale,
                "in_trade": pending is not None,
            }
        )

        if pending is not None:
            ent_i, tmpl, ent_regime, _ = pending
            if i >= ent_i + hold:
                ch_e = get_chain(days[ent_i])
                spy_x = float(features.reindex([dt]).iloc[0]["close"])
                fn = trade_fn_for_kind(tmpl.trade_kind)
                pnl = fn(ch_e, ch, spy_c, spy_x, tmpl.params)
                pending = None
                if pnl is not None and math.isfinite(pnl):
                    trades.append(
                        {
                            "regime": ent_regime.regime,
                            "action": tmpl.action,
                            "trade_kind": tmpl.trade_kind,
                            "label": tmpl.label,
                            "entry_date": _norm(days[ent_i]).strftime("%Y-%m-%d"),
                            "exit_date": dt.strftime("%Y-%m-%d"),
                            "hold_sessions": int(hold),
                            "pnl_usd": float(pnl),
                            "rationale": ent_regime.rationale,
                        }
                    )

        if pending is not None:
            continue

        tmpl = ACTION_TEMPLATES.get(st.action)
        if tmpl is None or not can_build_trade(tmpl.trade_kind, ch, tmpl.params):
            continue
        pending = (i, tmpl, st, spy_c)

    daily = pd.DataFrame(daily_rows)
    daily["date"] = pd.to_datetime(daily["date"])
    daily = daily.set_index("date").sort_index()
    return trades, daily


def build_states_only(features: pd.DataFrame) -> pd.DataFrame:
    """Daily regime/action report without options execution."""
    rows: list[dict[str, Any]] = []
    for dt, row in features.iterrows():
        st = classify_regime(_feature_tuple_from_row(row))
        rows.append(
            {
                "date": pd.Timestamp(dt).strftime("%Y-%m-%d"),
                "close": float(row["close"]),
                "vrp30": float(row["vrp30"]) if pd.notna(row.get("vrp30")) else None,
                "hurst": float(row["hurst"]) if pd.notna(row.get("hurst")) else None,
                "skew25": float(row["skew25"]) if pd.notna(row.get("skew25")) else None,
                "term_ratio": float(row["term_ratio"]) if pd.notna(row.get("term_ratio")) else None,
                "spot_vol_corr": float(row["spot_vol_corr"]) if pd.notna(row.get("spot_vol_corr")) else None,
                "ma_dist": float(row["ma_dist"]) if pd.notna(row.get("ma_dist")) else None,
                "vrp_state": st.feature_state[0],
                "hurst_state": st.feature_state[1],
                "skew_state": st.feature_state[2],
                "term_state": st.feature_state[3],
                "spot_vol_state": st.feature_state[4],
                "ma_dist_state": st.feature_state[5],
                "regime": st.regime,
                "action": st.action,
                "rationale": st.rationale,
            }
        )
    out = pd.DataFrame(rows)
    out["date"] = pd.to_datetime(out["date"])
    return out.set_index("date").sort_index()
