"""Sina commodity options provider.

新浪商品期权（期货期权）数据提供方实现。
"""

from __future__ import annotations

import akshare as ak
import pandas as pd

from ..cache import cache
from .commodity_base import CommodityOptionsDataProvider

_HIST_COLUMNS = [
    "timestamp",
    "symbol",
    "open",
    "high",
    "low",
    "close",
    "volume",
    "open_interest",
    "settlement",
]

_CHAIN_COLUMNS = [
    "underlying",
    "contract",
    "symbol",
    "name",
    "option_type",
    "strike",
    "expiration",
    "price",
    "change",
    "pct_change",
    "volume",
    "open_interest",
    "implied_volatility",
]


class SinaCommodityOptionsProvider(CommodityOptionsDataProvider):
    """Adapter for Sina commodity options datasets via AkShare.

    Supported endpoints:
        - ak.option_commodity_contract_sina(symbol): contract month listing
        - ak.option_commodity_contract_table_sina(symbol, contract): month chain table
        - ak.option_commodity_hist_sina(symbol): single option historical OHLCV
    """

    @cache(
        "options_chain_cache",
        key=lambda self, commodity_symbol=None: (
            f"commodity_contracts_{(commodity_symbol or self.commodity_symbol or '').strip()}"
        ),
    )
    def get_contracts(self, commodity_symbol: str | None = None) -> list[str]:
        """Fetch available contract months for a commodity option product.

        Args:
            commodity_symbol: 品种名称（如 '黄金期权'）。为 None 时使用初始化值。

        Returns:
            list[str]: 排序后的合约代码列表。

        Raises:
            ValueError: commodity_symbol 缺失或获取失败时抛出。
        """
        symbol = (commodity_symbol or self.commodity_symbol or "").strip()
        if not symbol:
            raise ValueError("commodity_symbol is required.")

        try:
            raw = ak.option_commodity_contract_sina(symbol=symbol)
            return self._normalize_contracts(raw)
        except Exception as e:
            raise ValueError(f"Failed to fetch commodity options contracts: {e}") from e

    @cache(
        "options_chain_cache",
        key=lambda self, contract=None: (
            f"commodity_chain_{(self.commodity_symbol or '').strip()}"
            f"_{(contract or self.contract or '').strip()}"
        ),
    )
    def get_options_chain(self, contract: str | None = None) -> pd.DataFrame:
        """Fetch the chain table for a contract month.

        Args:
            contract: 合约代码（如 'au2604'）。传 'main' 自动选持仓量最大的合约。
                为 None 时使用初始化值。

        Returns:
            pd.DataFrame: 标准化期权链（long format，含 call/put）。

        Raises:
            ValueError: 参数缺失或获取失败时抛出。
        """
        commodity_symbol = (self.commodity_symbol or "").strip()
        contract_value = (contract or self.contract or "").strip()
        if not commodity_symbol:
            raise ValueError("commodity_symbol is required.")
        if not contract_value:
            raise ValueError("contract is required.")

        if contract_value.lower() == "main":
            contract_value = self._resolve_main_contract(commodity_symbol)

        try:
            raw_df = ak.option_commodity_contract_table_sina(
                symbol=commodity_symbol, contract=contract_value
            )
            if raw_df is None or raw_df.empty:
                return pd.DataFrame(columns=_CHAIN_COLUMNS)
            return self._normalize_chain(raw_df, commodity_symbol, contract_value)
        except Exception as e:
            raise ValueError(f"Failed to fetch commodity options chain: {e}") from e

    @cache(
        "hist_data_cache",
        key=lambda self, symbol, start_date="1970-01-01", end_date="2030-12-31": (
            f"commodity_options_hist_{symbol}_{start_date}_{end_date}"
        ),
    )
    def get_options_history(
        self,
        symbol: str,
        start_date: str = "1970-01-01",
        end_date: str = "2030-12-31",
    ) -> pd.DataFrame:
        """Fetch historical OHLCV for a single commodity option contract.

        Args:
            symbol: 期权合约代码（如 'au2604C1200'）。
            start_date: 开始日期 (YYYY-MM-DD)。
            end_date: 结束日期 (YYYY-MM-DD)。

        Returns:
            pd.DataFrame: timestamp/symbol/open/high/low/close/volume/open_interest/settlement

        Raises:
            ValueError: 参数非法或获取失败时抛出。
        """
        symbol = str(symbol).strip()
        if not symbol:
            raise ValueError("symbol is required.")

        try:
            raw_df = ak.option_commodity_hist_sina(symbol=symbol)
            if raw_df is None or raw_df.empty:
                return pd.DataFrame(columns=_HIST_COLUMNS)

            df = raw_df.copy()
            df = self._normalize_hist_columns(df)
            df = self._filter_date_range(df, start_date, end_date)
            df["symbol"] = symbol

            numeric_cols = ["open", "high", "low", "close", "volume", "open_interest", "settlement"]
            for col in numeric_cols:
                if col in df.columns:
                    df[col] = pd.to_numeric(df[col], errors="coerce")

            for col in ["open_interest", "settlement"]:
                if col not in df.columns:
                    df[col] = None

            return df[[c for c in _HIST_COLUMNS if c in df.columns]].reset_index(drop=True)
        except Exception as e:
            raise ValueError(f"Failed to fetch commodity option history: {e}") from e

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _resolve_main_contract(self, commodity_symbol: str) -> str:
        """Resolve the main (dominant) contract by total open interest.

        遍历所有合约月份，选持仓量最大的作为主力合约。

        Args:
            commodity_symbol: 品种名称（如 '黄金期权'）。

        Returns:
            str: 主力合约代码（如 'au2604'）。

        Raises:
            ValueError: 无法获取合约列表或持仓数据时抛出。
        """
        contracts = self.get_contracts(commodity_symbol)
        if not contracts:
            raise ValueError(f"No contracts found for {commodity_symbol}")

        best_contract = contracts[0]
        best_oi = -1.0

        for c in contracts:
            try:
                raw_df = ak.option_commodity_contract_table_sina(
                    symbol=commodity_symbol, contract=c
                )
                if raw_df is None or raw_df.empty:
                    continue
                total_oi = 0.0
                for col in raw_df.columns:
                    if "持仓量" in str(col):
                        total_oi += pd.to_numeric(raw_df[col], errors="coerce").sum()
                if total_oi > best_oi:
                    best_oi = total_oi
                    best_contract = c
            except Exception:
                continue

        return best_contract

    @staticmethod
    def _normalize_contracts(raw: object) -> list[str]:
        """Normalize upstream contract listing into a sorted list of strings.

        Args:
            raw: Return value from ak.option_commodity_contract_sina.

        Returns:
            list[str]: Sorted unique contract codes.
        """
        contracts: list[str]

        if isinstance(raw, list):
            contracts = [str(x).strip() for x in raw if str(x).strip()]
        elif isinstance(raw, pd.DataFrame):
            if raw.empty:
                return []
            col = next(
                (
                    c
                    for c in ["合约", "合约代码", "合约名称", "contract", "代码"]
                    if c in raw.columns
                ),
                raw.columns[0],
            )
            contracts = raw[col].dropna().astype(str).str.strip().tolist()
        else:
            try:
                contracts = [str(x).strip() for x in list(raw)]  # type: ignore[arg-type]
            except Exception as e:
                raise ValueError(
                    "Unexpected response type for option_commodity_contract_sina."
                ) from e

        seen: set[str] = set()
        unique: list[str] = []
        for c in contracts:
            if c and c not in seen:
                unique.append(c)
                seen.add(c)
        return sorted(unique)

    @staticmethod
    def _pick_column(df: pd.DataFrame, candidates: list[str]) -> str | None:
        """Pick first existing column from candidates.

        Args:
            df: Input DataFrame.
            candidates: Candidate column names.

        Returns:
            str | None: Selected column or None.
        """
        for col in candidates:
            if col in df.columns:
                return col
        return None

    @classmethod
    def _find_strike_column(cls, df: pd.DataFrame) -> str | None:
        """Find strike column name from common patterns.

        Args:
            df: Raw chain DataFrame.

        Returns:
            str | None: Strike column name.
        """
        exact = cls._pick_column(df, ["行权价", "执行价", "strike", "K"])
        if exact is not None:
            return exact
        for col in df.columns:
            if isinstance(col, str) and ("行权价" in col or "执行价" in col):
                return col
        return None

    @classmethod
    def _extract_side(
        cls,
        df: pd.DataFrame,
        strike_col: str,
        prefix: str,
        option_type: str,
    ) -> pd.DataFrame:
        """Extract one side (call/put) from wide-format chain table.

        Args:
            df: Raw wide-format chain DataFrame.
            strike_col: Strike column name.
            prefix: Column prefix keyword (e.g., '看涨合约' / '看跌合约').
            option_type: 'call' or 'put'.

        Returns:
            pd.DataFrame: Side rows with standardized columns.
        """
        option_type_cn = "看涨" if option_type == "call" else "看跌"
        symbol_col = cls._pick_column(
            df,
            [
                f"{prefix}-{option_type_cn}期权合约",
                f"{prefix}-标识",
                f"{prefix}-合约代码",
                f"{prefix}-代码",
            ],
        )
        name_col = cls._pick_column(
            df, [f"{prefix}-名称", f"{prefix}-合约简称", f"{prefix}-期权简称"]
        )
        price_col = cls._pick_column(
            df, [f"{prefix}-最新价", f"{prefix}-收盘价", f"{prefix}-结算价", f"{prefix}-最新"]
        )
        volume_col = cls._pick_column(df, [f"{prefix}-成交量", f"{prefix}-成交", f"{prefix}-买量"])
        oi_col = cls._pick_column(df, [f"{prefix}-持仓量", f"{prefix}-持仓"])
        iv_col = cls._pick_column(df, [f"{prefix}-隐含波动率", f"{prefix}-IV"])
        change_col = cls._pick_column(df, [f"{prefix}-涨跌", f"{prefix}-涨跌额"])
        pct_col = cls._pick_column(df, [f"{prefix}-涨跌幅", f"{prefix}-涨跌幅%"])

        return pd.DataFrame(
            {
                "strike": df[strike_col],
                "symbol": df[symbol_col] if symbol_col else None,
                "name": df[name_col] if name_col else None,
                "option_type": option_type,
                "price": df[price_col] if price_col else None,
                "change": df[change_col] if change_col else None,
                "pct_change": df[pct_col] if pct_col else None,
                "volume": df[volume_col] if volume_col else None,
                "open_interest": df[oi_col] if oi_col else None,
                "implied_volatility": df[iv_col] if iv_col else None,
            }
        )

    @classmethod
    def _normalize_chain(
        cls,
        raw_df: pd.DataFrame,
        commodity_symbol: str,
        contract: str,
    ) -> pd.DataFrame:
        """Normalize commodity chain output to long format and standard columns.

        Args:
            raw_df: Raw chain table from AkShare.
            commodity_symbol: 品种名称。
            contract: 合约代码。

        Returns:
            pd.DataFrame: Standardized chain DataFrame.

        Raises:
            ValueError: If strike column cannot be identified.
        """
        df = raw_df.copy()
        strike_col = cls._find_strike_column(df)
        if strike_col is None:
            raise ValueError("Strike column not found in commodity options chain output.")

        calls = cls._extract_side(
            df, strike_col=strike_col, prefix="看涨合约", option_type="call"
        )
        puts = cls._extract_side(
            df, strike_col=strike_col, prefix="看跌合约", option_type="put"
        )
        out = pd.concat([calls, puts], ignore_index=True)

        out["underlying"] = commodity_symbol
        out["contract"] = contract
        out["expiration"] = None

        if "symbol" in out.columns:
            out["symbol"] = out["symbol"].astype(str).str.strip()
            invalid = out["symbol"].isin(["", "None", "nan", "NaN"])
            out = out[~invalid].reset_index(drop=True)

        for col in [
            "strike",
            "price",
            "change",
            "pct_change",
            "volume",
            "open_interest",
            "implied_volatility",
        ]:
            if col in out.columns:
                out[col] = pd.to_numeric(out[col], errors="coerce")

        return out[[c for c in _CHAIN_COLUMNS if c in out.columns]].reset_index(drop=True)

    @staticmethod
    def _normalize_hist_columns(df: pd.DataFrame) -> pd.DataFrame:
        """Rename Chinese column names to standard English names.

        Args:
            df: Raw history DataFrame.

        Returns:
            pd.DataFrame: Renamed DataFrame.
        """
        column_map = {
            "日期": "timestamp",
            "date": "timestamp",
            "datetime": "timestamp",
            "时间": "timestamp",
            "开盘": "open",
            "最高": "high",
            "最低": "low",
            "收盘": "close",
            "成交量": "volume",
            "持仓量": "open_interest",
            "hold": "open_interest",
            "结算价": "settlement",
        }
        rename = {}
        for src, target in column_map.items():
            if src in df.columns and src != target:
                rename[src] = target
        if rename:
            df = df.rename(columns=rename)

        if "timestamp" not in df.columns and hasattr(df, "index") and len(df.index) > 0:
            df = df.reset_index()
            if df.columns[0] != "timestamp":
                df = df.rename(columns={df.columns[0]: "timestamp"})

        if "timestamp" in df.columns:
            df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
            ts = df["timestamp"]
            if getattr(ts.dt, "tz", None) is None:
                df["timestamp"] = ts.dt.tz_localize("Asia/Shanghai")

        return df

    @staticmethod
    def _filter_date_range(df: pd.DataFrame, start_date: str, end_date: str) -> pd.DataFrame:
        """Filter DataFrame by date range.

        Args:
            df: DataFrame with 'timestamp' column.
            start_date: Start date (YYYY-MM-DD).
            end_date: End date (YYYY-MM-DD).

        Returns:
            pd.DataFrame: Filtered DataFrame.
        """
        if "timestamp" not in df.columns:
            return df
        start_dt = pd.to_datetime(start_date, errors="raise")
        end_dt = pd.to_datetime(end_date, errors="raise")
        ts = df["timestamp"]
        if getattr(ts.dt, "tz", None) is not None:
            start_dt = start_dt.tz_localize(ts.dt.tz)
            end_dt = end_dt.tz_localize(ts.dt.tz)
        return df[(ts >= start_dt) & (ts <= end_dt)]
