"""
IndicatorSpec Model

Provides the IndicatorSpec class for specifying indicator configurations with frequency, shift, and instrument.
"""

from typing import Annotated, Any, Optional, Union

import polars as pl
from pydantic import BaseModel, Field, field_validator

from ..enums import Freq
from ..indicators import (
    SMAIndicator,
    EMAIndicator,
    SMMAIndicator,
    WMAIndicator,
    ATRIndicator,
    ATRQuantileIndicator,
    SuperTrendIndicator,
    MACDIndicator,
    ADXIndicator,
    RSIIndicator,
    CCIIndicator,
    StochasticIndicator,
    BollingerBandsIndicator,
    MarketProfileIndicator,
    RawOhlcvIndicator,
)


# ============================================================================
# Indicator Discriminated Union (强类型 Indicator 配置)
# ============================================================================

IndicatorConfig = Annotated[
    Union[
        # 移动平均类
        SMAIndicator,
        EMAIndicator,
        SMMAIndicator,
        WMAIndicator,
        # 波动率类
        ATRIndicator,
        ATRQuantileIndicator,
        # 趋势类
        SuperTrendIndicator,
        MACDIndicator,
        ADXIndicator,
        # 动量类
        RSIIndicator,
        CCIIndicator,
        StochasticIndicator,
        # 其他
        BollingerBandsIndicator,
        MarketProfileIndicator,
        RawOhlcvIndicator,
    ],
    Field(discriminator='type')
]
"""
强类型 Indicator 配置

使用 Pydantic discriminated union，根据 'type' 字段自动选择正确的模型：
- type="SMA" → SMAIndicator
- type="ATR" → ATRIndicator
- type="SuperTrend" → SuperTrendIndicator
- ... etc

优点：
- 编译时类型检查
- IDE 自动补全
- 参数验证
- 自动 JSON 序列化/反序列化

Example:
    >>> # 方式 1: 直接创建强类型模型
    >>> atr = ATRIndicator(period=21)
    >>>
    >>> # 方式 2: 从 Dict 自动转换（API 兼容）
    >>> indicator: IndicatorConfig = {"type": "ATR", "period": 21}
    >>> # Pydantic 自动识别为 ATRIndicator
"""


class IndicatorSpec(BaseModel):
    """指標規範（强类型版本）"""

    instrument_id: Optional[str] = Field(
        None, description="交易標的 ID（可選，用於多商品場景）"
    )
    freq: Freq = Field(..., description="頻率 (Freq enum)")
    shift: int = Field(1, description="位移（預設 1）")
    indicator: IndicatorConfig = Field(
        ..., description="指標配置（强类型 Pydantic 模型）"
    )

    @field_validator("freq", mode="before")
    @classmethod
    def convert_freq(cls, v: Any) -> Freq:
        """自動轉換字串為 Freq enum（保持 API 兼容性）"""
        if isinstance(v, str):
            try:
                return Freq(v)
            except ValueError:
                valid_values = [e.value for e in Freq]
                raise ValueError(
                    f"Invalid freq: '{v}'. "
                    f"Valid values: {', '.join(valid_values)}"
                )
        return v

    @field_validator("indicator", mode="before")
    @classmethod
    def convert_indicator(cls, v: Any) -> IndicatorConfig:
        """自動轉換 Dict 為強類型 Indicator 模型（保持 API 兼容性）

        接受：
        - Dict[str, Any]: 從 API 返回或舊代碼（自動轉換）
        - IndicatorConfig: 新代碼使用強類型模型

        Pydantic 會根據 'type' 字段自動選擇正確的模型。
        """
        # Dict 會被 Pydantic 自動轉換為對應的 Indicator 模型
        # 強類型模型直接返回
        return v

    def short_name(self) -> str:
        """生成指標簡短名稱（與 Rust 的 short_name 一致）

        格式範例:
        - SMA: "SMA|20.close"
        - EMA: "EMA|12.close"
        - ATR: "ATR|14"
        - SuperTrend: "ST|3.0x_atr"

        Returns:
            指標簡稱字符串

        Raises:
            ValueError: 未知的指標類型
        """
        indicator_type = self.indicator.type

        if indicator_type == "SMA":
            return f"SMA|{self.indicator.period}.{self.indicator.column}"

        elif indicator_type == "EMA":
            return f"EMA|{self.indicator.period}.{self.indicator.column}"

        elif indicator_type == "SMMA":
            return f"SMMA|{self.indicator.period}.{self.indicator.column}"

        elif indicator_type == "WMA":
            return f"WMA|{self.indicator.period}.{self.indicator.column}"

        elif indicator_type == "ATR":
            # ATR 始終返回單一數值欄位，無 quantile
            return f"ATR|{self.indicator.period}"

        elif indicator_type == "AtrQuantile":
            # 將 quantile 轉為百分比（例如 0.5 -> 50）
            quantile_pct = int(self.indicator.quantile * 100)
            # 格式：ATRQ|{atr_column}_Q{quantile%}_{window}
            return f"ATRQ|{self.indicator.atr_column}_Q{quantile_pct}_{self.indicator.window}"

        elif indicator_type == "SuperTrend":
            return f"ST|{self.indicator.multiplier}x_{self.indicator.volatility_column}"

        elif indicator_type == "MarketProfile":
            # 提取 anchor_config 資訊
            anchor_config = self.indicator.anchor_config
            end_rule = anchor_config.get("end_rule", {})
            lookback_days = anchor_config.get("lookback_days", 1)
            tick_size = self.indicator.tick_size

            # 格式化時間字串（支持新舊兩種格式）
            rule_type = end_rule.get("type")

            if rule_type == "DailyTime" or "DailyTime" in end_rule:
                # 新格式: {"type": "DailyTime", "hour": 9, "minute": 15}
                # 舊格式: {"DailyTime": {"hour": 9, "minute": 15}}
                if rule_type == "DailyTime":
                    time_str = f"{end_rule['hour']:02d}{end_rule['minute']:02d}"
                else:
                    daily = end_rule["DailyTime"]
                    time_str = f"{daily['hour']:02d}{daily['minute']:02d}"

            elif rule_type == "WeeklyTime" or "WeeklyTime" in end_rule:
                # 新格式: {"type": "WeeklyTime", "weekday": "Mon", "hour": 9, "minute": 15}
                # 舊格式: {"WeeklyTime": {"weekday": 0, "hour": 9, "minute": 15}}
                if rule_type == "WeeklyTime":
                    weekday = end_rule["weekday"]
                    # weekday 可能是字串 ("Mon") 或整數 (0)
                    if isinstance(weekday, str):
                        # 字串格式: "Mon" -> "Mon"
                        weekday_str = weekday
                    else:
                        # 整數格式: 0 -> "Mon"
                        weekday_map = {
                            0: "Mon",
                            1: "Tue",
                            2: "Wed",
                            3: "Thu",
                            4: "Fri",
                            5: "Sat",
                            6: "Sun",
                        }
                        weekday_str = weekday_map.get(weekday, str(weekday))
                    time_str = (
                        f"W{weekday_str}_{end_rule['hour']:02d}{end_rule['minute']:02d}"
                    )
                else:
                    weekly = end_rule["WeeklyTime"]
                    weekday = weekly["weekday"]
                    if isinstance(weekday, str):
                        weekday_str = weekday
                    else:
                        weekday_map = {
                            0: "Mon",
                            1: "Tue",
                            2: "Wed",
                            3: "Thu",
                            4: "Fri",
                            5: "Sat",
                            6: "Sun",
                        }
                        weekday_str = weekday_map.get(weekday, str(weekday))
                    time_str = (
                        f"W{weekday_str}_{weekly['hour']:02d}{weekly['minute']:02d}"
                    )
            else:
                time_str = "UNKNOWN"

            # 格式: MP|{time}_{lookback_days}_{tick_size}
            # 注意：MarketProfile 不支持 indicator 層級的 shift
            # TODO: 臨時方案 - 整數顯示為整數，非整數保留小數（與 Rust 行為一致）
            #       詳見 docs/issues/tick-size-float-formatting-mismatch.md
            tick_str = str(int(tick_size)) if tick_size == int(tick_size) else str(tick_size)
            return f"MP|{time_str}_{lookback_days}_{tick_str}"

        elif indicator_type == "CCI":
            return f"CCI|{self.indicator.period}"

        elif indicator_type == "RSI":
            return f"RSI|{self.indicator.period}"

        elif indicator_type == "BollingerBands":
            return f"BB|{self.indicator.period}_{self.indicator.num_std}"

        elif indicator_type == "MACD":
            return f"MACD|{self.indicator.fast_period}_{self.indicator.slow_period}_{self.indicator.signal_period}"

        elif indicator_type == "Stochastic":
            return f"STOCH|{self.indicator.k_period}_{self.indicator.d_period}"

        elif indicator_type == "ADX":
            return f"ADX|{self.indicator.period}"

        elif indicator_type == "RawOhlcv":
            # RawOhlcv 只返回 column 名稱
            return self.indicator.column

        else:
            raise ValueError(f"未知的指標類型: {indicator_type}")

    def display_name(self) -> str:
        """生成完整的 column 名稱（與 Rust 的 display_name 一致）

        格式:
        - 有 instrument_id: "{instrument_id}_{freq}_{indicator_short_name}[_s{shift}]"
        - 無 instrument_id: "{freq}_{indicator_short_name}[_s{shift}]"

        範例:
        - "ES_1min_SMA|20.close"      (shift=1，不顯示)
        - "1D_ATR|14_s2"               (shift=2)
        - "BTC_5min_EMA|12.close_s2"  (shift=2)
        - "1h_ST|3.0x_atr"            (無 instrument_id)

        Returns:
            完整的 column 名稱字符串
        """
        parts = []

        # 1. instrument_id（如果有）
        if self.instrument_id:
            parts.append(self.instrument_id)

        # 2. freq（處理 Freq enum）
        freq_str = self.freq.value if isinstance(self.freq, Freq) else self.freq
        parts.append(freq_str)

        # 3. indicator short name
        parts.append(self.short_name())

        # 4. shift（只在非預設值時加入）
        if self.shift != 1:
            parts.append(f"s{self.shift}")

        return "_".join(parts)

    def col(self) -> pl.Expr:
        """返回 Polars column expression（最常用的便捷方法）

        Returns:
            pl.col(display_name) 的 Polars 表達式

        Example:
            >>> spec = IndicatorSpec(
            ...     instrument_id="ES",
            ...     freq="1min",
            ...     shift=1,
            ...     indicator={"type": "SMA", "period": 20, "column": "close"}
            ... )
            >>> # 直接在條件中使用
            >>> condition = spec.col() > 100
            >>> # 或在過濾中使用
            >>> df.filter(spec.col() > pl.col("open"))
        """
        return pl.col(self.display_name())
