#!/usr/bin/env python3
"""Regime-aware parameter bank.

Different market regimes demand different strategy parameters.  This module
maintains a parameter bank keyed by regime (TRENDING / RANGING / BREAKOUT)
so strategies can auto-switch their configuration when the regime changes.

Usage::

    from regime_aware_params import RegimeParamBank

    bank = RegimeParamBank()
    trending_params = bank.get_params("TRENDING")
    params = bank.get_params_for_adx(adx_value=28)
"""

from __future__ import annotations

import json
from copy import deepcopy
from datetime import datetime
from pathlib import Path
from typing import Any

import config_manager


# -- default parameter sets per regime -----------------------------------

_DEFAULT_REGIME_PARAMS: dict[str, dict[str, Any]] = {
    "TRENDING": {
        "ema_short": 9,
        "ema_long": 21,
        "sl_atr": 1.0,
        "tp_atr": 2.0,
        "min_confidence": 0.65,
        "trail_atr": 0.8,
        "break_even_atr": 0.5,
        "max_hold_bars": 0,  # 0 = no time limit in trends
        "partial_close_pct": 0.4,
        "position_boost": 1.0,
        "note": "趋势跟随：宽松止损，大止盈，不设时间限制",
    },
    "RANGING": {
        "ema_short": 7,
        "ema_long": 21,
        "sl_atr": 0.5,
        "tp_atr": 1.0,
        "min_confidence": 0.40,
        "trail_atr": 0.4,
        "break_even_atr": 0.3,
        "max_hold_bars": 12,  # 1 hour in M5
        "partial_close_pct": 0.5,
        "position_boost": 0.5,
        "note": "震荡反向：紧止损，快止盈，设时间止损",
    },
    "BREAKOUT": {
        "ema_short": 12,
        "ema_long": 26,
        "sl_atr": 1.5,
        "tp_atr": 3.0,
        "min_confidence": 0.40,
        "trail_atr": 1.0,
        "break_even_atr": 0.8,
        "max_hold_bars": 0,
        "partial_close_pct": 0.3,
        "position_boost": 1.2,
        "note": "突破：宽止损防假突破，大止盈吃趋势",
    },
}

# ADX thresholds for regime classification
REGIME_THRESHOLDS = {
    "trending_min_adx": 25,
    "breakout_min_adx": 35,
    "ranging_max_adx": 20,
}


class RegimeParamBank:
    """Parameter bank keyed by market regime.

    Supports:
      - Built-in defaults per regime
      - Runtime overrides loaded from disk
      - Merge of optimized params per regime (from Bayesian optimizer)
    """

    def __init__(self):
        self._bank: dict[str, dict[str, Any]] = deepcopy(_DEFAULT_REGIME_PARAMS)
        self._load_overrides()
        self._load_optimized()

    # -- paths ------------------------------------------------------------

    def _overrides_path(self) -> Path:
        return config_manager.get_config_dir() / "regime_params.json"

    def _optimized_path(self) -> Path:
        return config_manager.get_data_dir() / "bayesian" / "regime_optimized.json"

    # -- loading ----------------------------------------------------------

    def _load_overrides(self) -> None:
        path = self._overrides_path()
        if not path.exists():
            return
        try:
            payload = json.loads(path.read_text(encoding="utf-8"))
            overrides = payload.get("regimes", payload)
            for regime, params in overrides.items():
                if regime in self._bank and isinstance(params, dict):
                    self._bank[regime].update(params)
        except Exception:
            pass

    def _load_optimized(self) -> None:
        path = self._optimized_path()
        if not path.exists():
            return
        try:
            payload = json.loads(path.read_text(encoding="utf-8"))
            optimized = payload.get("regimes", payload)
            for regime, params in optimized.items():
                if regime in self._bank and isinstance(params, dict):
                    self._bank[regime].update(params)
        except Exception:
            pass

    # -- query ------------------------------------------------------------

    def get_params(self, regime: str) -> dict[str, Any]:
        """Return parameter dict for the given regime."""
        r = str(regime).upper().strip()
        if r not in self._bank:
            r = "RANGING"
        return deepcopy(self._bank[r])

    def get_params_for_adx(self, adx_value: float) -> dict[str, Any]:
        """Select params based on ADX value."""
        if adx_value >= REGIME_THRESHOLDS["breakout_min_adx"]:
            return self.get_params("BREAKOUT")
        if adx_value >= REGIME_THRESHOLDS["trending_min_adx"]:
            return self.get_params("TRENDING")
        return self.get_params("RANGING")

    def classify_regime(self, adx_value: float) -> str:
        """Return regime string for an ADX value."""
        if adx_value >= REGIME_THRESHOLDS["breakout_min_adx"]:
            return "BREAKOUT"
        if adx_value >= REGIME_THRESHOLDS["trending_min_adx"]:
            return "TRENDING"
        return "RANGING"

    def list_regimes(self) -> list[str]:
        return sorted(self._bank.keys())

    # -- mutation ----------------------------------------------------------

    def set_params(self, regime: str, params: dict[str, Any]) -> None:
        """Override parameters for a regime."""
        r = str(regime).upper().strip()
        if r not in self._bank:
            self._bank[r] = {}
        self._bank[r].update(params)

    def save_overrides(self) -> None:
        """Persist current bank to the overrides file."""
        path = self._overrides_path()
        path.parent.mkdir(parents=True, exist_ok=True)
        payload = {
            "updated_at": datetime.now().isoformat(),
            "regimes": self._bank,
        }
        path.write_text(
            json.dumps(payload, indent=2, ensure_ascii=False, default=str),
            encoding="utf-8",
        )

    def save_optimized(self, regime_params: dict[str, dict[str, Any]]) -> None:
        """Persist optimized regime parameters (from Bayesian study)."""
        path = self._optimized_path()
        path.parent.mkdir(parents=True, exist_ok=True)
        payload = {
            "updated_at": datetime.now().isoformat(),
            "source": "bayesian_optimizer",
            "regimes": regime_params,
        }
        path.write_text(
            json.dumps(payload, indent=2, ensure_ascii=False, default=str),
            encoding="utf-8",
        )

    def reset_to_defaults(self) -> None:
        self._bank = deepcopy(_DEFAULT_REGIME_PARAMS)


_DEFAULT_BANK: RegimeParamBank | None = None


def get_regime_param_bank() -> RegimeParamBank:
    global _DEFAULT_BANK
    if _DEFAULT_BANK is None:
        _DEFAULT_BANK = RegimeParamBank()
    return _DEFAULT_BANK
