"""Options indicator calculator interface.

期权衍生指标计算器抽象接口（Track B）。
"""

from abc import ABC, abstractmethod
from typing import Literal

import pandas as pd

PCRBy = Literal["volume", "open_interest"]


class OptionsIndicatorCalculator(ABC):
    """Options indicator calculator interface.

    期权衍生指标计算器抽象接口（Track B）。

    Inputs are standardized DataFrames from ``OptionsDataProvider.get_options_chain()``
    or real-time snapshots.

    Expected ``chain_df`` columns:
        symbol, option_type ('call'/'put'), strike, expiration,
        volume, open_interest, implied_volatility, price
    """

    @abstractmethod
    def get_pcr(self, chain_df: pd.DataFrame, by: PCRBy) -> pd.DataFrame:
        """Calculate Put-Call Ratio.

        计算 PCR，可按成交量或持仓量。

        Args:
            chain_df: Options chain DataFrame.
            by: 'volume' or 'open_interest'.

        Returns:
            pd.DataFrame:
            - pcr: Put/Call ratio
            - put_value: Put side aggregated value
            - call_value: Call side aggregated value

        Raises:
            ValueError: If required columns are missing.
        """
        pass

    @abstractmethod
    def get_max_pain(self, chain_df: pd.DataFrame) -> pd.DataFrame:
        """Calculate Max Pain.

        计算最大痛点。

        Args:
            chain_df: Options chain DataFrame.

        Returns:
            pd.DataFrame:
            - max_pain_strike: 最大痛点行权价
            - total_pain: 总痛苦值

        Raises:
            ValueError: If required columns are missing.
        """
        pass

    @abstractmethod
    def get_iv_rank(self, iv_series: pd.DataFrame, window: int) -> pd.DataFrame:
        """Calculate IV Rank: (current - min) / (max - min) within rolling window.

        返回值范围 [0, 1]（quantile 惯例，非百分比）。乘以 100 可得百分比形式。

        Args:
            iv_series: Historical IV with columns [timestamp, iv].
            window: Lookback window length.

        Returns:
            pd.DataFrame: [timestamp, iv, iv_rank] where iv_rank is in [0, 1].

        Raises:
            ValueError: If required columns are missing.
        """
        pass

    @abstractmethod
    def get_iv_percentile(self, iv_series: pd.DataFrame, window: int) -> pd.DataFrame:
        """Calculate IV Percentile: fraction of prior values below current within rolling window.

        返回值范围 [0, 1]（quantile 惯例，非百分比）。乘以 100 可得百分比形式。

        Args:
            iv_series: Historical IV with columns [timestamp, iv].
            window: Lookback window length.

        Returns:
            pd.DataFrame: [timestamp, iv, iv_percentile] where iv_percentile is in [0, 1].

        Raises:
            ValueError: If required columns are missing.
        """
        pass

    @abstractmethod
    def get_iv_smile(
        self,
        chain_df: pd.DataFrame,
        expiration: str,
        underlying_price: float | None = None,
    ) -> pd.DataFrame:
        """Get IV smile curve for a specific expiration using OTM contracts.

        Standard approach: use OTM call (K >= ATM) + OTM put (K <= ATM).

        Args:
            chain_df: Options chain DataFrame.
            expiration: Expiration date filter.
            underlying_price: Spot price for moneyness and OTM filtering.

        Returns:
            pd.DataFrame: [expiration, strike, iv, moneyness, iv_source]

        Raises:
            ValueError: If required columns are missing.
        """
        pass

    @abstractmethod
    def get_iv_term_structure(
        self, chain_df: pd.DataFrame, underlying_price: float | None = None
    ) -> pd.DataFrame:
        """Calculate IV term structure across expirations.

        提取各到期日 ATM 附近 IV，构建期限结构。若提供 underlying_price 则用最接近
        标的价格的行权价作为 ATM；否则回退至 median strike 近似。

        Args:
            chain_df: Options chain DataFrame.
            underlying_price: Underlying/spot price for ATM identification. Optional.

        Returns:
            pd.DataFrame: [expiration, iv_atm]

        Raises:
            ValueError: If required columns are missing.
        """
        pass
