#!/usr/bin/env python3
"""
Strategy settings dict-view model.
"""

from __future__ import annotations

from typing import Any, Dict, List

from core.modules.data_contract.contract_const import DataKey
from core.modules.market_profile.constants import DEFAULT_PROFILE_ID
from core.modules.data_contract.kline_keys import (
    STOCK_KLINE_DATA_ID_VALUES,
    kline_term_from_data_id_value,
)

from .goal_settings import StrategyGoalSettings
from .simulation_settings import StrategySimulationSettings


class StrategySettingsView:
    def __init__(self, settings_dict: Dict[str, Any]):
        self._settings = settings_dict
        meta = settings_dict.get("meta") if isinstance(settings_dict.get("meta"), dict) else {}
        self.display_name = str(meta.get("display_name") or "").strip()
        self.description = str(meta.get("description") or "").strip()
        self.keywords = list(meta.get("keywords") or []) if isinstance(meta.get("keywords"), list) else []
        self.name = self.display_name or "unknown"
        self.is_enabled = settings_dict.get("is_enabled", False)
        self.core = settings_dict.get("core", {})
        self.data = settings_dict.get("data", {})
        self.sampling = settings_dict.get("sampling", {})
        self.price_simulator = settings_dict.get("price_simulator", {})
        self.goal = settings_dict.get("goal", {})
        self.performance = settings_dict.get("performance", {})

    @property
    def market_profile(self) -> str:
        raw = self._settings.get("market_profile", DEFAULT_PROFILE_ID)
        pid = str(raw or DEFAULT_PROFILE_ID).strip()
        return pid or DEFAULT_PROFILE_ID

    @property
    def simulation(self) -> Dict[str, Any]:
        """根级 ``simulation`` 原始 dict 片段（与 ``StrategySettingsView`` 其它块一致）。"""
        s = self._settings.get("simulation")
        return s if isinstance(s, dict) else {}

    @property
    def simulation_settings(self) -> StrategySimulationSettings:
        """与 ``StrategySettings.simulation`` 同构；盯盘/买卖价模型等用其属性读取。"""
        return StrategySimulationSettings.from_strategy_root(self._settings)

    @property
    def goal_settings(self) -> StrategyGoalSettings:
        return StrategyGoalSettings.from_strategy_root(self._settings)

    @property
    def stock_status_risk(self):
        return self.goal_settings.stock_status_risk

    @staticmethod
    def normalize_indicators(raw: Any) -> Dict[str, Any]:
        if raw is None:
            return {}
        if not isinstance(raw, dict):
            raise ValueError("indicators 必须为 dict")
        return dict(raw)

    @staticmethod
    def normalize_base_required_data(raw: Dict[str, Any]) -> Dict[str, Any]:
        if not isinstance(raw, dict):
            raise ValueError("data.base_required_data 必须为 dict")
        params = raw.get("params")
        if params is None:
            params = {}
        elif not isinstance(params, dict):
            raise ValueError("data.base_required_data.params 必须为 dict")

        raw_id = raw.get("data_id")
        if raw_id is None or (isinstance(raw_id, str) and not raw_id.strip()):
            data_id = DataKey.STOCK_KLINE_DAILY.value
        else:
            data_id = str(raw_id).strip()
            if data_id not in STOCK_KLINE_DATA_ID_VALUES:
                raise ValueError(
                    "data.base_required_data.data_id 须为 "
                    f"{DataKey.STOCK_KLINE_DAILY.value!r}、"
                    f"{DataKey.STOCK_KLINE_WEEKLY.value!r} 或 "
                    f"{DataKey.STOCK_KLINE_MONTHLY.value!r}；"
                    f"收到 {data_id!r}"
                )

        merged = dict(params)
        if "adjust" not in merged or (
            isinstance(merged.get("adjust"), str)
            and not str(merged.get("adjust")).strip()
        ):
            merged["adjust"] = "qfq"

        return {
            "data_id": data_id,
            "params": merged,
            "indicators": StrategySettingsView.normalize_indicators(raw.get("indicators")),
        }

    @staticmethod
    def normalize_extra_required_data_item(item: Dict[str, Any]) -> Dict[str, Any]:
        if not isinstance(item, dict):
            raise ValueError("数据源项必须为 dict")
        raw_id = item.get("data_id")
        if not raw_id or not str(raw_id).strip():
            raise ValueError("extra_required_data_sources 每项必须包含非空的 data_id")
        data_id = str(raw_id).strip()

        params = item.get("params")
        if params is None:
            params = {}
        elif not isinstance(params, dict):
            raise ValueError("数据源 params 必须为 dict")

        if data_id in STOCK_KLINE_DATA_ID_VALUES:
            merged = dict(params)
            if "adjust" not in merged or (
                isinstance(merged.get("adjust"), str)
                and not str(merged.get("adjust")).strip()
            ):
                merged["adjust"] = "qfq"
            return {
                "data_id": data_id,
                "params": merged,
                "indicators": StrategySettingsView.normalize_indicators(item.get("indicators")),
            }

        return {
            "data_id": data_id,
            "params": dict(params),
            "indicators": StrategySettingsView.normalize_indicators(item.get("indicators")),
        }

    @staticmethod
    def validate_data_config(data: Dict[str, Any]) -> None:
        if "indicators" in data:
            raise ValueError(
                "data.indicators 已移除；请在 base_required_data 或 extra_required_data_sources 各项上声明 indicators"
            )
        base = data.get("base_required_data")
        if not isinstance(base, dict):
            raise ValueError("data.base_required_data 必须为 dict")
        StrategySettingsView.normalize_base_required_data(base)

        extra = data.get("extra_required_data_sources", [])
        if extra is None:
            return
        if not isinstance(extra, list):
            raise ValueError("data.extra_required_data_sources 必须为 list")
        for i, item in enumerate(extra):
            if not isinstance(item, dict):
                raise ValueError(f"data.extra_required_data_sources[{i}] 必须为 dict")
            try:
                StrategySettingsView.normalize_extra_required_data_item(item)
            except ValueError as e:
                raise ValueError(f"data.extra_required_data_sources[{i}]: {e}") from e

    @property
    def base_required_data(self) -> Dict[str, Any]:
        b = self.data.get("base_required_data")
        if not isinstance(b, dict):
            raise ValueError("缺少 data.base_required_data")
        return b

    @property
    def extra_required_data_sources(self) -> List[Dict[str, Any]]:
        xs = self.data.get("extra_required_data_sources", [])
        if xs is None or not isinstance(xs, list):
            return []
        return list(xs)

    @property
    def required_data_sources(self) -> List[Dict[str, Any]]:
        """Contract 注入边界：base + extras；未在此列表的 DataKey 由回测编排自行加载。"""
        base = self.normalize_base_required_data(self.base_required_data)
        rest = [
            self.normalize_extra_required_data_item(x)
            for x in self.extra_required_data_sources
        ]
        return [base] + rest

    @property
    def resolved_base_required_data(self) -> Dict[str, Any]:
        return self.normalize_base_required_data(self.base_required_data)

    @property
    def min_required_records(self) -> int:
        return int(self.data.get("min_required_records", 100) or 100)

    @property
    def adjust_type(self) -> str:
        p = self.resolved_base_required_data.get("params") or {}
        return str(p.get("adjust", "qfq"))

    @property
    def indicators(self) -> Dict[str, Any]:
        """Base K 线 slot 上的 indicators（Workbench 图表等）。"""
        return self.normalize_indicators(self.base_required_data.get("indicators"))

    @property
    def tag_storage_entity_type(self) -> str:
        p = self.resolved_base_required_data.get("params") or {}
        explicit = str(p.get("tag_storage_entity_type") or "").strip()
        if explicit:
            return explicit
        data_id = str(self.resolved_base_required_data.get("data_id") or "").strip()
        if data_id in STOCK_KLINE_DATA_ID_VALUES:
            return f"stock_kline_{kline_term_from_data_id_value(data_id)}"
        return "stock_kline_daily"

    @property
    def base_kline_term(self) -> str:
        data_id = str(self.resolved_base_required_data.get("data_id") or "").strip()
        if data_id in STOCK_KLINE_DATA_ID_VALUES:
            return kline_term_from_data_id_value(data_id)
        return "daily"

    @property
    def start_date(self) -> str:
        s = self.sampling if isinstance(self.sampling, dict) else {}
        return str(s.get("start_date", "") or "").strip()

    @property
    def end_date(self) -> str:
        s = self.sampling if isinstance(self.sampling, dict) else {}
        return str(s.get("end_date", "") or "").strip()

    @property
    def sampling_amount(self) -> int:
        return int(self.sampling.get("sampling_amount", 10) or 10)

    @property
    def sampling_config(self) -> Dict[str, Any]:
        return self.sampling

    @property
    def max_workers(self) -> Any:
        simulator_cfg = self.price_simulator or {}
        enumerator_cfg = self.get("enumerator") or {}
        performance_cfg = self.performance or {}
        return (
            simulator_cfg.get("max_workers")
            or enumerator_cfg.get("max_workers")
            or performance_cfg.get("max_workers")
            or "auto"
        )

    def to_dict(self) -> Dict[str, Any]:
        return self._settings

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "StrategySettingsView":
        return cls(data)

    def get(self, key: str, default: Any = None) -> Any:
        return self._settings.get(key, default)


__all__ = ["StrategySettingsView"]
