"""StrategyParams — 策略參數基底類別。

子類別定義 Pydantic fields 作為策略參數。
必要 base fields: instrument, base_freq。
提供 strategy_name 自動命名和 sweep() 參數掃描。
"""

from __future__ import annotations

import re
from abc import ABC, abstractmethod
from itertools import product
from typing import TYPE_CHECKING, Any, Self
from zoneinfo import ZoneInfo

from pydantic import BaseModel, ConfigDict, Field

from ..enums import Freq, TradeDirection

if TYPE_CHECKING:
    from .config import StrategyConfig


def _camel_to_snake(name: str) -> str:
    """CamelCase → snake_case."""
    return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()


class StrategyParams(BaseModel, ABC):
    """策略參數基底類別。

    子類別定義 Pydantic fields 作為策略參數，
    strategy_name 從 class name + 所有 fields 自動產生，
    sweep() 產生參數組合供掃描。

    Example:
        class MpIbPocBullParams(StrategyParams):
            mp_anchor_hour: int = Field(ge=0, le=23)
            tick_size: float = Field(default=5.0, gt=0)

        p = MpIbPocBullParams(
            instrument="FTMO:swap:US100.cash",
            base_freq=Freq.MIN_15,
            mp_anchor_hour=11,
        )
        print(p.strategy_name)
        # "{'name': 'mp_ib_poc_bull_params', 'instrument': ...}"

        swept = p.sweep({"mp_anchor_hour": [9, 11, 13]})
        # → 3 個 MpIbPocBullParams 實例
    """

    model_config = ConfigDict(arbitrary_types_allowed=True)

    instrument: str = Field(description="商品代碼")
    base_freq: Freq = Field(description="基準頻率")
    trade_direction: TradeDirection = Field(description="交易方向")
    tz: ZoneInfo = Field(
        default_factory=lambda: ZoneInfo("UTC"),
        description="策略時區（IANA），用於將 OHLCV ts 從 UTC 轉為指定時區後評估時間條件。",
    )

    @property
    def strategy_name(self) -> str:
        """從所有 model fields 自動組裝 dict → str 作為策略名稱。

        - "name" = class name (CamelCase → snake_case)
        - 所有 field 按定義順序列出
        - Enum 值自動取 .value
        - 結果穩定（field 順序由 model_fields 決定）
        """
        cls_name = _camel_to_snake(type(self).__name__)
        d: dict[str, object] = {"name": cls_name}
        for field_name in self.model_fields:
            val = getattr(self, field_name)
            if hasattr(val, "value"):  # Enum
                d[field_name] = val.value
            elif isinstance(val, ZoneInfo):
                d[field_name] = val.key
            else:
                d[field_name] = val
        return str(d)

    def _sweep(self, param_grid: dict[str, list[Any]]) -> list[Self]:
        """產生參數組合（搭配 frange() 快速建立數值範圍）。

        Args:
            param_grid: 要掃描的參數 {field_name: [value1, value2, ...]}
                        未列出的 field 使用當前值

        Returns:
            list[Self]: 每組參數組合一個實例

        Example:
            >>> from tradepose_client import frange
            >>> params._sweep({
            ...     "mp_anchor_hour": [9, 11, 13],
            ...     "tick_size": frange(3, 7, 2),  # [3.0, 5.0, 7.0]
            ... })
            # → 9 個實例
        """
        base = self.model_dump()
        keys = list(param_grid.keys())
        values = list(param_grid.values())
        results: list[Self] = []

        for combo in product(*values):
            params = {**base}
            for k, v in zip(keys, combo):
                params[k] = v
            results.append(type(self).model_validate(params))

        return results

    @abstractmethod
    def create_strategy(self) -> StrategyConfig:
        """建立對應的 StrategyConfig（由子類別實作）。"""
        ...
