"""Research-only D48 XSMOM 28-day V2 candidate and matched no-op."""

from __future__ import annotations

import sys
from pathlib import Path

import pandas as pd
from pandas import DataFrame, Series

RESEARCH_STRATEGY_DIR = Path(__file__).resolve().parent
VOLATILITY_STRATEGY_DIR = RESEARCH_STRATEGY_DIR.parent / "volatility_breakout"
for strategy_dir in (RESEARCH_STRATEGY_DIR, VOLATILITY_STRATEGY_DIR):
    if str(strategy_dir) not in sys.path:
        sys.path.insert(0, str(strategy_dir))

from v5_d48_riskcap import VolatilityBreakoutRiskCap  # noqa: E402
from xsmom_28d_v2_math import (  # noqa: E402
    RankMatrices,
    apply_v2_gate,
    attach_signal_time_rank_state,
    build_rank_matrices,
)

FROZEN_PAIRS = (
    "BTC/USDT:USDT",
    "ETH/USDT:USDT",
    "SOL/USDT:USDT",
    "BNB/USDT:USDT",
    "XRP/USDT:USDT",
    "DOGE/USDT:USDT",
    "ADA/USDT:USDT",
    "ZEC/USDT:USDT",
    "EDGE/USDT:USDT",
    "POWER/USDT:USDT",
    "VANRY/USDT:USDT",
    "UNI/USDT:USDT",
)

# Exact contents are frozen by the source hash. The originating JSON artifact is
# binance-futures-listing-dates.json, SHA-256:
LISTING_DATES_SOURCE_SHA256 = (
    "0d939ba3a53d16d658640862c2dd6bf4726d634c3c60da0ce5c36037cf29e9dc"
)
FROZEN_LISTING_TIMES = {
    "ADA/USDT:USDT": "2020-01-31T08:00:00Z",
    "BNB/USDT:USDT": "2020-02-10T08:00:00Z",
    "BTC/USDT:USDT": "2019-09-08T17:55:00Z",
    "DOGE/USDT:USDT": "2020-07-10T09:00:00Z",
    "EDGE/USDT:USDT": "2026-03-19T14:00:00Z",
    "ETH/USDT:USDT": "2019-11-27T07:45:00Z",
    "POWER/USDT:USDT": "2025-12-06T09:00:00Z",
    "SOL/USDT:USDT": "2020-09-14T07:00:00Z",
    "UNI/USDT:USDT": "2020-09-18T07:00:00Z",
    "VANRY/USDT:USDT": "2024-03-13T17:30:00Z",
    "XRP/USDT:USDT": "2020-01-06T08:20:00Z",
    "ZEC/USDT:USDT": "2020-02-05T08:00:00Z",
}


class VolatilityBreakoutXsmom28dV2Base(VolatilityBreakoutRiskCap):
    """Shared causal rank plumbing; subclasses change only gate enforcement."""

    enforce_rank_gate = False

    def informative_pairs(self):
        return [(pair, "1d") for pair in FROZEN_PAIRS]

    def _daily_close(self, pair: str) -> Series:
        frame = self.dp.get_pair_dataframe(pair, "1d")
        if frame.empty:
            return Series(dtype="float64")
        dates = pd.to_datetime(frame["date"], utc=True, errors="raise")
        return Series(
            pd.to_numeric(frame["close"], errors="coerce").to_numpy(),
            index=pd.DatetimeIndex(dates),
            dtype="float64",
        )

    def _rank_matrices(self) -> RankMatrices:
        daily_closes = {pair: self._daily_close(pair) for pair in FROZEN_PAIRS}
        signature = tuple(
            (
                pair,
                len(series),
                series.index.max().isoformat() if len(series) else None,
            )
            for pair, series in sorted(daily_closes.items())
        )
        cached = getattr(self, "_xsmom_rank_matrices", None)
        if cached is None or signature != getattr(
            self, "_xsmom_rank_matrices_signature", None
        ):
            cached = build_rank_matrices(
                daily_closes,
                FROZEN_LISTING_TIMES,
            )
            self._xsmom_rank_matrices = cached
            self._xsmom_rank_matrices_signature = signature
        return cached

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = super().populate_indicators(dataframe, metadata)
        pair = metadata["pair"]
        if pair not in FROZEN_LISTING_TIMES:
            raise ValueError(f"pair is outside frozen XSMOM universe: {pair}")
        return attach_signal_time_rank_state(
            dataframe,
            pair=pair,
            matrices=self._rank_matrices(),
        )

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = super().populate_entry_trend(dataframe, metadata)
        return apply_v2_gate(
            dataframe,
            enforce_rank_gate=self.enforce_rank_gate,
        )


class VolatilityBreakoutXsmom28dV2NoOp(VolatilityBreakoutXsmom28dV2Base):
    """Matched implementation-isolation control: compute ranks, pass all signals."""

    enforce_rank_gate = False

    def version(self) -> str:
        return "research-d48-riskcap-xsmom28d-v2-noop"


class VolatilityBreakoutXsmom28dV2(VolatilityBreakoutXsmom28dV2Base):
    """V2: cold-start pass-through, otherwise long top4 / short bottom4."""

    enforce_rank_gate = True

    def version(self) -> str:
        return "research-d48-riskcap-xsmom28d-v2"
