from __future__ import annotations

import copy
import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

import config_manager
from strategy_sdk.types import StrategyManifest


@dataclass(frozen=True)
class BuiltinStrategySpec:
    strategy_id: str
    name: str
    entrypoint: str
    kind: str = "signal"
    description: str = ""
    aliases: tuple[str, ...] = ()
    capabilities: tuple[str, ...] = ()
    version: str = "legacy-2.1.0"
    base_strategy_id: str = ""
    default_mode: str = "builtin"
    trust_level: str = "builtin"
    params: dict[str, Any] = field(default_factory=dict)


class StrategyRegistry:
    def __init__(self):
        self._specs: dict[str, BuiltinStrategySpec] = {}
        self._aliases: dict[str, str] = {}
        self._register_builtin_defaults()

    def _register_builtin_defaults(self) -> None:
        self.register_builtin(
            BuiltinStrategySpec(
                strategy_id="ema_macd",
                name="EMA MACD",
                entrypoint="signal_generators.ema_macd_signal",
                description="EMA + MACD built-in signal generator",
                aliases=("ema", "default"),
                capabilities=("intraday", "long_short", "trend_following"),
            )
        )
        self.register_builtin(
            BuiltinStrategySpec(
                strategy_id="elliott_wave",
                name="Elliott Wave",
                entrypoint="signal_generators.elliott_wave_signal",
                description="Elliott wave structure strategy (NOT YET IMPLEMENTED)",
                aliases=("elliott", "wave"),
                capabilities=("structure", "swing"),
                trust_level="unimplemented",
            )
        )
        self.register_builtin(
            BuiltinStrategySpec(
                strategy_id="chanlun",
                name="Chanlun",
                entrypoint="signal_generators.chanlun_signal",
                description="Chanlun structure strategy (NOT YET IMPLEMENTED)",
                aliases=("chan", "缠论", "缠"),
                capabilities=("structure", "range"),
                trust_level="unimplemented",
            )
        )
        self.register_builtin(
            BuiltinStrategySpec(
                strategy_id="wyckoff",
                name="Wyckoff",
                entrypoint="signal_generators.wyckoff_signal",
                description="Wyckoff volume structure strategy",
                aliases=("wyck",),
                capabilities=("range", "volume"),
            )
        )
        self.register_builtin(
            BuiltinStrategySpec(
                strategy_id="composite",
                name="Composite",
                entrypoint="signal_generators.composite_signal",
                kind="composer",
                description="Built-in weighted composite strategy",
                aliases=("multi", "hybrid"),
                capabilities=("composition", "voting"),
            )
        )

    def register_builtin(self, spec: BuiltinStrategySpec) -> None:
        self._specs[spec.strategy_id] = spec
        self._aliases[spec.strategy_id] = spec.strategy_id
        for alias in spec.aliases:
            self._aliases[alias] = spec.strategy_id

    def _strategy_market_state_file(self) -> Path:
        return config_manager.get_config_dir() / "strategy_market_state.json"

    def _load_market_state_assets(self) -> dict[str, BuiltinStrategySpec]:
        state_file = self._strategy_market_state_file()
        if not state_file.exists():
            return {}
        try:
            payload = json.loads(state_file.read_text(encoding="utf-8"))
        except Exception:
            return {}
        strategies = payload.get("strategies", {}) if isinstance(payload, dict) else {}
        if not isinstance(strategies, dict):
            return {}
        assets: dict[str, BuiltinStrategySpec] = {}
        for raw_strategy_id, row in strategies.items():
            if not isinstance(row, dict):
                continue
            strategy_id = str(raw_strategy_id or "").strip()
            if not strategy_id or strategy_id in self._specs:
                continue
            base_strategy_id = str(
                row.get("base_engine")
                or ("composite" if bool(row.get("is_composite", False)) else "ema_macd")
                or "ema_macd"
            ).strip()
            base_strategy_id = self._aliases.get(base_strategy_id, base_strategy_id)
            base_spec = self._specs.get(base_strategy_id, self._specs["ema_macd"])
            template = (
                copy.deepcopy(row.get("template", {}))
                if isinstance(row.get("template", {}), dict)
                else {}
            )
            optimized = row.get("optimized", {}) if isinstance(row.get("optimized", {}), dict) else {}
            optimized_params = (
                optimized.get("params", {})
                if isinstance(optimized.get("params", {}), dict)
                else {}
            )
            params = copy.deepcopy(template)
            params.update(copy.deepcopy(optimized_params))
            if bool(row.get("is_composite", False)):
                components = [
                    str(item).strip()
                    for item in row.get("components", [])
                    if str(item).strip()
                ] if isinstance(row.get("components", []), list) else []
                template_components = (
                    template.get("components", [])
                    if isinstance(template.get("components", []), list)
                    else []
                )
                if not components:
                    components = [
                        str(item).strip()
                        for item in template_components
                        if str(item).strip()
                    ]
                weights = (
                    copy.deepcopy(template.get("weights", {}))
                    if isinstance(template.get("weights", {}), dict)
                    else {}
                )
                if components:
                    params["composite_strategies"] = components
                if weights:
                    params["composite_weights"] = weights
            description = str(
                row.get("source_document")
                or row.get("description")
                or f"Workspace strategy asset backed by {base_strategy_id}"
            )
            assets[strategy_id] = BuiltinStrategySpec(
                strategy_id=strategy_id,
                name=str(row.get("display_name") or row.get("name") or strategy_id),
                entrypoint=base_spec.entrypoint,
                kind="composer" if bool(row.get("is_composite", False)) else base_spec.kind,
                description=description,
                capabilities=base_spec.capabilities,
                version=str(row.get("current_version") or "1.0.0"),
                base_strategy_id=base_strategy_id,
                default_mode="asset",
                trust_level="workspace_asset",
                params=params,
            )
        return assets

    def resolve(self, strategy_id: str | None) -> str:
        key = str(strategy_id or "ema_macd").strip()
        if key in self._aliases:
            return self._aliases[key]
        dynamic_specs = self._load_market_state_assets()
        return key if key in dynamic_specs else key

    def get_spec(self, strategy_id: str | None) -> BuiltinStrategySpec:
        resolved = self.resolve(strategy_id)
        if resolved in self._specs:
            return self._specs[resolved]
        dynamic_specs = self._load_market_state_assets()
        if resolved not in dynamic_specs:
            raise KeyError(f"Unknown strategy_id: {strategy_id}")
        return dynamic_specs[resolved]

    def list_manifests(self) -> list[StrategyManifest]:
        manifests: list[StrategyManifest] = []
        dynamic_specs = self._load_market_state_assets()
        for spec in [*self._specs.values(), *dynamic_specs.values()]:
            manifests.append(
                StrategyManifest(
                    strategy_id=spec.strategy_id,
                    name=spec.name,
                    version=spec.version,
                    kind=spec.kind,
                    entrypoint=spec.entrypoint,
                    description=spec.description,
                    capabilities=list(spec.capabilities),
                    supported_symbols=["XAUUSD"],
                    supported_timeframes=["M1", "M5", "M15", "H1"],
                    default_mode=spec.default_mode,
                    trust_level=spec.trust_level,
                )
            )
        return manifests

    def instantiate_legacy(self, strategy_id: str | None, params: dict[str, Any] | None = None):
        from signal_generators import create_generator
        from strategies.adapters.legacy_signal_adapter import LegacySignalStrategyAdapter

        spec = self.get_spec(strategy_id)
        create_strategy_id = spec.base_strategy_id or spec.strategy_id
        merged_params = copy.deepcopy(spec.params)
        if isinstance(params, dict):
            merged_params.update(copy.deepcopy(params))
        legacy = create_generator(create_strategy_id, merged_params or None)
        return LegacySignalStrategyAdapter(spec, legacy)


_DEFAULT_REGISTRY: StrategyRegistry | None = None


def get_strategy_registry() -> StrategyRegistry:
    global _DEFAULT_REGISTRY
    if _DEFAULT_REGISTRY is None:
        _DEFAULT_REGISTRY = StrategyRegistry()
    return _DEFAULT_REGISTRY
