"""
SPDR sector/industry ETF universe for Zarattini & Antonacci Timing Industry (Section 6).

Table 6 from *A Century of Profitable Industry Trends* — 31 State Street ETFs with
database inception dates (paper uses Yahoo Finance daily closes).
"""

from __future__ import annotations

from pathlib import Path

import numpy as np
import pandas as pd
import yfinance as yf

from RenTech.strategy_stack.french_industry_loader import ensure_french_factors_daily, load_french_factors_daily
from RenTech.strategy_stack.industry_trend_timing_engine import (
    N_INDUSTRIES,
    IndustryTrendTimingResult,
    run_trend_timing_panel,
)

N_SPDR_ETFS = 31

# (ticker, paper database start) — month aligned to first full month in paper window
SPDR_TIMING_ETFS: tuple[tuple[str, str], ...] = (
    ("XLF", "2005-01-01"),
    ("XLK", "2005-01-01"),
    ("XLE", "2005-01-01"),
    ("XLV", "2005-01-01"),
    ("XLI", "2005-01-01"),
    ("XBI", "2006-02-01"),
    ("XLU", "2005-01-01"),
    ("XLP", "2005-01-01"),
    ("XLY", "2005-01-01"),
    ("KRE", "2006-06-01"),
    ("XLB", "2005-01-01"),
    ("XLC", "2018-06-01"),
    ("XRT", "2006-06-01"),
    ("XOP", "2006-06-01"),
    ("XLRE", "2015-10-01"),
    ("XHB", "2006-02-01"),
    ("KBE", "2005-11-01"),
    ("XME", "2006-06-01"),
    ("KIE", "2005-11-01"),
    ("XSD", "2006-02-01"),
    ("XAR", "2011-09-01"),
    ("XES", "2006-06-01"),
    ("KCE", "2005-11-01"),
    ("XNTK", "2005-01-01"),
    ("XHE", "2011-01-01"),
    ("XSW", "2011-09-01"),
    ("XPH", "2006-06-01"),
    ("XTN", "2011-01-01"),
    ("XHS", "2011-09-01"),
    ("XITK", "2016-01-01"),
    ("XTL", "2011-01-01"),
)

SPDR_TIMING_TICKERS: list[str] = [t for t, _ in SPDR_TIMING_ETFS]
SPDR_INCEPTION: dict[str, pd.Timestamp] = {
    t: pd.Timestamp(d) for t, d in SPDR_TIMING_ETFS
}


def _download_closes(tickers: list[str], start: str, end: str) -> pd.DataFrame:
    raw = yf.download(
        tickers,
        start=start,
        end=end,
        auto_adjust=True,
        progress=False,
        group_by="column",
    )
    if isinstance(raw.columns, pd.MultiIndex):
        close = raw["Close"].copy()
    else:
        close = raw[["Close"]].copy()
        close.columns = [tickers[0]]
    close.index = pd.to_datetime(close.index).tz_localize(None)
    return close.sort_index().ffill(limit=5)


def load_spdr_timing_etf_returns(
    *,
    start: str,
    end: str,
    bench_ticker: str = "SPY",
) -> tuple[pd.DataFrame, pd.Series, pd.Series]:
    """
    Daily decimal returns for the 31-ETF universe, Ken French RF, and benchmark.

    ETFs are masked to NaN before each ticker's paper inception date.
    """
    fetch_start = (pd.Timestamp(start) - pd.DateOffset(months=14)).strftime("%Y-%m-%d")
    fetch_end = (pd.Timestamp(end) + pd.DateOffset(days=5)).strftime("%Y-%m-%d")

    all_tickers = sorted(set(SPDR_TIMING_TICKERS + [bench_ticker]))
    prices = _download_closes(all_tickers, fetch_start, fetch_end)
    rets = prices.pct_change()

    etf_rets = rets[SPDR_TIMING_TICKERS].copy()
    for t, inception in SPDR_INCEPTION.items():
        if t in etf_rets.columns:
            etf_rets.loc[etf_rets.index < inception, t] = np.nan

    ensure_french_factors_daily()
    factors = load_french_factors_daily()
    rf = factors["RF"].reindex(etf_rets.index).ffill()

    if bench_ticker in rets.columns:
        mkt = rets[bench_ticker].reindex(etf_rets.index)
    else:
        mkt = etf_rets.mean(axis=1)

    t0, t1 = pd.Timestamp(start), pd.Timestamp(end)
    mask = (etf_rets.index >= t0) & (etf_rets.index <= t1)
    return etf_rets.loc[mask], rf.loc[mask], mkt.loc[mask]


def run_spdr_industry_trend_timing(
    *,
    start: str = "2005-01-03",
    end: str = "2024-03-28",
    capital: float = 100_000.0,
    bench_ticker: str = "SPY",
    target_vol: float = 0.015,
    max_leverage: float = 2.0,
    max_not_trade: float = 1.0,
) -> IndustryTrendTimingResult:
    etf_rets, rf, mkt = load_spdr_timing_etf_returns(
        start=start,
        end=end,
        bench_ticker=bench_ticker,
    )

    return run_trend_timing_panel(
        etf_rets,
        rf=rf,
        mkt_rets=mkt,
        start=start,
        end=end,
        capital=capital,
        n_universe=N_SPDR_ETFS,
        universe_label=f"31 SPDR sector/industry ETFs (Table 6; Yahoo {bench_ticker} benchmark)",
        strategy_label="industry_trend_timing_spdr31",
        benchmark_label=f"{bench_ticker} total return",
        target_vol=target_vol,
        max_leverage=max_leverage,
        max_not_trade=max_not_trade,
        extra_meta={
            "etfs": SPDR_TIMING_TICKERS,
            "inception_dates": {t: str(d.date()) for t, d in SPDR_INCEPTION.items()},
            "paper_reference_2005_2024": {
                "timing_etfs_cagr_pct": 7.7,
                "timing_etfs_vol_pct": 13.7,
                "timing_etfs_sharpe": 0.61,
                "timing_etfs_max_dd_pct": -24.0,
                "timing_etfs_alpha_ann_pct": 2.7,
                "spy_cagr_pct": 10.0,
                "note": "Paper Table 7 includes $0.0035/share commissions",
            },
        },
    )
