"""Simple futures indicator calculator.

基于 pandas 的期货指标计算器实现。
"""

import numpy as np
import pandas as pd

from .base import FuturesIndicatorCalculator


class SimpleFuturesIndicatorCalculator(FuturesIndicatorCalculator):
    """Pure-pandas futures indicator calculator.

    纯 pandas 实现的期货指标计算器。
    """

    def get_oi_change(self, oi_df: pd.DataFrame) -> pd.DataFrame:
        """Calculate open interest change.

        Args:
            oi_df: DataFrame with [timestamp, open_interest].

        Returns:
            pd.DataFrame: [timestamp, open_interest, open_interest_change]
        """
        self._require_columns(oi_df, ["open_interest"])
        df = oi_df.copy()
        df["open_interest"] = pd.to_numeric(df["open_interest"], errors="coerce")
        df["open_interest_change"] = df["open_interest"].diff()
        cols = [c for c in ["timestamp", "open_interest", "open_interest_change"]
                if c in df.columns]
        return df[cols].reset_index(drop=True)

    def get_price_oi_divergence(
        self, close_df: pd.DataFrame, oi_df: pd.DataFrame
    ) -> pd.DataFrame:
        """Analyze price vs open interest divergence.

        Args:
            close_df: DataFrame with [timestamp, close].
            oi_df: DataFrame with [timestamp, open_interest].

        Returns:
            pd.DataFrame: [timestamp, close, open_interest, signal]
        """
        self._require_columns(close_df, ["timestamp", "close"])
        self._require_columns(oi_df, ["timestamp", "open_interest"])

        df = pd.merge(close_df, oi_df, on="timestamp", how="inner")
        df["open_interest"] = pd.to_numeric(df["open_interest"], errors="coerce")
        price_chg = df["close"].diff()
        oi_chg = df["open_interest"].diff()

        conditions = [
            (price_chg > 0) & (oi_chg > 0),   # 价涨仓增: 多头开仓
            (price_chg > 0) & (oi_chg < 0),   # 价涨仓减: 空头平仓
            (price_chg < 0) & (oi_chg > 0),   # 价跌仓增: 空头开仓
            (price_chg < 0) & (oi_chg < 0),   # 价跌仓减: 多头平仓
        ]
        choices = ["long_open", "short_close", "short_open", "long_close"]
        df["signal"] = np.select(conditions, choices, default="neutral")

        return df[["timestamp", "close", "open_interest", "signal"]].reset_index(drop=True)

    def get_vwap(self, ohlcv_df: pd.DataFrame) -> pd.DataFrame:
        """Calculate VWAP.

        Args:
            ohlcv_df: OHLCV DataFrame with [timestamp, high, low, close, volume].

        Returns:
            pd.DataFrame: [timestamp, vwap]
        """
        self._require_columns(ohlcv_df, ["volume"])
        df = ohlcv_df.copy()

        if "high" in df.columns and "low" in df.columns and "close" in df.columns:
            typical_price = (df["high"] + df["low"] + df["close"]) / 3
        elif "close" in df.columns:
            typical_price = df["close"]
        else:
            raise ValueError("Need at least 'close' column for VWAP.")

        cumulative_tp_vol = (typical_price * df["volume"]).cumsum()
        cumulative_vol = df["volume"].cumsum()
        df["vwap"] = cumulative_tp_vol / cumulative_vol

        cols = [c for c in ["timestamp", "vwap"] if c in df.columns]
        return df[cols].reset_index(drop=True)

    def get_term_structure(self, quotes_df: pd.DataFrame) -> pd.DataFrame:
        """Calculate term structure metrics.

        Args:
            quotes_df: Multi-contract quotes with [contract, price, timestamp].

        Returns:
            pd.DataFrame: [timestamp, curve_slope, curve_level]
        """
        self._require_columns(quotes_df, ["contract", "price"])
        df = quotes_df.sort_values("contract").reset_index(drop=True)

        prices = df["price"].dropna()
        slope = 0.0 if len(prices) < 2 else float(
            np.polyfit(range(len(prices)), prices, 1)[0]
        )

        level = float(prices.mean()) if len(prices) > 0 else 0.0

        result = {"curve_slope": slope, "curve_level": level}
        if "timestamp" in df.columns:
            result["timestamp"] = df["timestamp"].iloc[0]

        return pd.DataFrame([result])

    def get_spread(self, near_df: pd.DataFrame, far_df: pd.DataFrame) -> pd.DataFrame:
        """Calculate inter-contract spread.

        Args:
            near_df: Near contract with [timestamp, close].
            far_df: Far contract with [timestamp, close].

        Returns:
            pd.DataFrame: [timestamp, spread]
        """
        self._require_columns(near_df, ["timestamp", "close"])
        self._require_columns(far_df, ["timestamp", "close"])

        df = pd.merge(
            near_df[["timestamp", "close"]].rename(columns={"close": "near_close"}),
            far_df[["timestamp", "close"]].rename(columns={"close": "far_close"}),
            on="timestamp",
            how="inner",
        )
        df["spread"] = df["near_close"] - df["far_close"]

        return df[["timestamp", "spread"]].reset_index(drop=True)

    def get_long_short_ratio(self, rank_df: pd.DataFrame) -> pd.DataFrame:
        """Calculate long/short ratio from position ranking.

        Args:
            rank_df: Position ranking DataFrame with long/short OI columns.

        Returns:
            pd.DataFrame: [date, exchange, variety, contract, long_short_ratio]
        """
        self._require_columns(rank_df, ["long_open_interest", "short_open_interest"])

        df = rank_df.copy()
        df["long_open_interest"] = pd.to_numeric(
            df["long_open_interest"], errors="coerce"
        ).fillna(0)
        df["short_open_interest"] = pd.to_numeric(
            df["short_open_interest"], errors="coerce"
        ).fillna(0)

        group_cols = [c for c in ["date", "exchange", "variety", "contract"]
                      if c in df.columns]
        if not group_cols:
            total_long = df["long_open_interest"].sum()
            total_short = df["short_open_interest"].sum()
            ratio = total_long / total_short if total_short > 0 else float("inf")
            return pd.DataFrame([{"long_short_ratio": ratio}])

        result = df.groupby(group_cols, as_index=False).agg(
            total_long=("long_open_interest", "sum"),
            total_short=("short_open_interest", "sum"),
        )
        result["long_short_ratio"] = np.where(
            result["total_short"] > 0,
            result["total_long"] / result["total_short"],
            float("inf"),
        )

        return result[group_cols + ["long_short_ratio"]].reset_index(drop=True)

    @staticmethod
    def _require_columns(df: pd.DataFrame, columns: list[str]) -> None:
        """Validate required columns exist.

        Raises:
            ValueError: If any column is missing.
        """
        missing = [c for c in columns if c not in df.columns]
        if missing:
            raise ValueError(f"Missing required columns: {missing}")
