"""
kis_trader/engine/momentum_env_keys.py — 모멘텀(MOMENTUM) env 키 단일 정의
==========================================================================
1분봉 단기 추세추격 — 스캘핑(SCALP reversal)과 분리.
config_momentum · momentum_engine · 웹 · 파라서치 공통.
"""
from __future__ import annotations

from typing import Any, Dict, Optional

# 레거시 → MOMENTUM (DB 마이그레이션·1회 승계용)
LEGACY_TO_MOMENTUM: Dict[str, str] = {
    "SCALP_MOM_RSI_MIN": "MOMENTUM_RSI_MIN",
    "SCALP_MOM_RSI_MAX": "MOMENTUM_RSI_MAX",
    "SCALP_MOM_VOL_MULT": "MOMENTUM_VOL_MULT",
    "SCALP_MOM_VOL_WIN": "MOMENTUM_VOL_WIN",
    "SCALP_MOM_TIME_END_HM": "MOMENTUM_TIME_END_HM",
    "SCALP_STOP_LOSS_PCT": "MOMENTUM_STOP_LOSS_PCT",
    "SCALP_TAKE_PROFIT_PCT": "MOMENTUM_TAKE_PROFIT_PCT",
    "SCALP_TP_MAX_PCT": "MOMENTUM_TP_MAX_PCT",
    "SCALP_SHOULDER_MIN_HIGH_PCT": "MOMENTUM_SHOULDER_MIN_HIGH_PCT",
    "SCALP_SHOULDER_CUT_PCT": "MOMENTUM_SHOULDER_CUT_PCT",
    "SCALP_MAX_DAILY": "MOMENTUM_MAX_DAILY",
    "SCALP_MIN_DROP_PCT_FOR_LOSS_CUT": "MOMENTUM_MIN_DROP_PCT_FOR_LOSS_CUT",
    "SCALP_MIN_PROFIT_PCT": "MOMENTUM_MIN_PROFIT_PCT",
    "SCALP_MIN_HOLD_SEC": "MOMENTUM_MIN_HOLD_SEC",
    "MAX_LOSS_PER_TRADE_KRW": "MOMENTUM_MAX_LOSS_PER_TRADE_KRW",
    "HIGH_CHASE_THR": "MOMENTUM_HIGH_CHASE_THR",
    "MAX_DAILY_CHG": "MOMENTUM_MAX_DAILY_CHG",
    "MIN_STOCK_PRICE": "MOMENTUM_MIN_PRICE",
    "SLOT_MONEY_DEFAULT": "MOMENTUM_SLOT_MONEY",
}

MOMENTUM_CONFIG_KEYS = frozenset({
    "MOMENTUM_RSI_MIN",
    "MOMENTUM_RSI_MAX",
    "MOMENTUM_VOL_MULT",
    "MOMENTUM_VOL_WIN",
    "MOMENTUM_TIME_END_HM",
    "MOMENTUM_TIME_START",
    "MOMENTUM_MAX_FROM_OPEN_PCT",
    "MOMENTUM_MIN_FROM_OPEN_PCT",
    "MOMENTUM_SKIP_HTS_SCAN_DUPES",
    "MOMENTUM_STOP_LOSS_PCT",
    "MOMENTUM_TAKE_PROFIT_PCT",
    "MOMENTUM_TP_MAX_PCT",
    "MOMENTUM_SHOULDER_MIN_HIGH_PCT",
    "MOMENTUM_SHOULDER_CUT_PCT",
    "MOMENTUM_RATCHET_TIERS",
    "MOMENTUM_TRAIL_PCT",
    "MOMENTUM_TRAIL_ARM_PCT",
    "MOMENTUM_MAX_HOLD_BARS",
    "MOMENTUM_MAX_DAILY",
    "MOMENTUM_MIN_PRICE",
    "MOMENTUM_MAX_DAILY_CHG",
    "MOMENTUM_HIGH_CHASE_THR",
    "MOMENTUM_USE_DEFENSE_FILTERS",
    "MOMENTUM_USE_HIGH_CHASE_FILTER",
    "MOMENTUM_USE_DAILY_RANGE_FILTER",
    "MOMENTUM_USE_EMA_FILTER",
    "MOMENTUM_EMA_FAST_PERIOD",
    "MOMENTUM_EMA_SLOW_PERIOD",
    "MOMENTUM_USE_RSI_MAX_FILTER",
    "MOMENTUM_PATTERN_BREAKOUT",
    "MOMENTUM_PATTERN_PULLBACK",
    "MOMENTUM_CHASE_LOOKBACK_MIN",
    "MOMENTUM_PULLBACK_LOOKBACK_MIN",
    "MOMENTUM_PULLBACK_MIN_PCT",
    "MOMENTUM_PULLBACK_MAX_PCT",
    "MOMENTUM_SETUP_VOL_MAX_MULT",
    "MOMENTUM_SETUP_BEAR_BARS_MIN",
    "MOMENTUM_MAX_LOSS_PER_TRADE_KRW",
    "MOMENTUM_MIN_DROP_PCT_FOR_LOSS_CUT",
    "MOMENTUM_MIN_PROFIT_PCT",
    "MOMENTUM_MIN_HOLD_SEC",
    "MOMENTUM_SLOT_MONEY",
    "MOMENTUM_MAX_STOCKS",
    "MOMENTUM_TOTAL_BUDGET_KRW",
    "MOMENTUM_MAX_BUY_AMOUNT",
    "MOMENTUM_RSI_PERIOD",
    "MOMENTUM_COOLDOWN_SEC",
    "MOMENTUM_LIVE_BACKTEST_ALIGN",
    "MOMENTUM_BACKTEST_USE_TICK_EXIT",
    "MOMENTUM_BACKTEST_USE_TICK_ENTRY",
    "MOMENTUM_BACKTEST_TICK_FALLBACK_OHLC",
    "MOMENTUM_BACKTEST_POLL_MS",
    "MOMENTUM_BACKTEST_SELL_SLIP_PCT",
    "MOMENTUM_BACKTEST_BUY_SLIP_PCT",
    "MOMENTUM_BACKTEST_UNIVERSE_STRICT",
    "MOMENTUM_BACKTEST_UNIVERSE_STRICT_LAG_MIN",
    "MOMENTUM_BACKTEST_LIVE_SCAN_QUEUE",
    "MOMENTUM_BACKTEST_SCAN_SEC",
    "MOMENTUM_BACKTEST_UNIVERSE_SCAN_AT",
    "MOMENTUM_BACKTEST_CANDLE_WARMUP_BARS",
    "MOMENTUM_UNIVERSE_EXIT_DEBOUNCE_SEC",
    "MOMENTUM_LIVE_SIGNAL_LOOKBACK_BARS",
    "MOMENTUM_EOD_ENABLED",
    "MOMENTUM_EOD_HM",
    "MOMENTUM_FORCE_EOD_EXIT",
})


def _row_val(row: Dict[str, Any], key: str, default: Any = None) -> Any:
    v = row.get(key)
    if v not in (None, "", "None"):
        return v
    return default


def momentum_env_float(row: Dict[str, Any], key: str, default: float) -> float:
    v = _row_val(row, key)
    if v is None:
        return float(default)
    try:
        return float(v)
    except (TypeError, ValueError):
        return float(default)


def momentum_env_int(row: Dict[str, Any], key: str, default: int) -> int:
    v = _row_val(row, key)
    if v is None:
        return int(default)
    try:
        return int(float(v))
    except (TypeError, ValueError):
        return int(default)


def momentum_env_bool(row: Dict[str, Any], key: str, default: bool) -> bool:
    v = _row_val(row, key)
    if v is None:
        return default
    s = str(v).strip().lower()
    if s in ("1", "true", "t", "y", "yes", "on"):
        return True
    if s in ("0", "false", "f", "n", "no", "off", ""):
        return False
    return default


def _legacy_float(row: Dict[str, Any], primary: str, legacy_keys: tuple, default: float) -> float:
    v = _row_val(row, primary)
    if v is not None:
        try:
            return float(v)
        except (TypeError, ValueError):
            pass
    for lk in legacy_keys:
        v = _row_val(row, lk)
        if v is not None:
            try:
                return float(v)
            except (TypeError, ValueError):
                continue
    return float(default)


def params_to_momentum_env_patch(p: Dict[str, Any]) -> Dict[str, str]:
    """파라서치·웹 params → MOMENTUM env 패치 (문자열)."""
    patch: Dict[str, str] = {}
    if not p:
        return patch

    def _set(k: str, v: Any) -> None:
        if v is not None and v != "":
            patch[k] = str(v)

    if "mom_rsi_min" in p:
        _set("MOMENTUM_RSI_MIN", p["mom_rsi_min"])
    if "mom_rsi_max" in p:
        _set("MOMENTUM_RSI_MAX", p["mom_rsi_max"])
    if "mom_vol_mult" in p:
        _set("MOMENTUM_VOL_MULT", p["mom_vol_mult"])
    if "mom_vol_win" in p:
        _set("MOMENTUM_VOL_WIN", p["mom_vol_win"])
    if "mom_time_end_hm" in p:
        _set("MOMENTUM_TIME_END_HM", int(float(p["mom_time_end_hm"])))
    if "mom_max_from_open_pct" in p:
        _set("MOMENTUM_MAX_FROM_OPEN_PCT", p["mom_max_from_open_pct"])
    if "mom_min_from_open_pct" in p:
        _set("MOMENTUM_MIN_FROM_OPEN_PCT", p["mom_min_from_open_pct"])
    if "sl_pct" in p:
        _set("MOMENTUM_STOP_LOSS_PCT", -abs(float(p["sl_pct"])))
    if "tp_pct" in p:
        _set("MOMENTUM_TAKE_PROFIT_PCT", p["tp_pct"])
    if "tp_max_pct" in p:
        _set("MOMENTUM_TP_MAX_PCT", p["tp_max_pct"])
    if "shoulder_min_high" in p:
        _set("MOMENTUM_SHOULDER_MIN_HIGH_PCT", p["shoulder_min_high"])
    if "shoulder_cut_pct" in p:
        _set("MOMENTUM_SHOULDER_CUT_PCT", p["shoulder_cut_pct"])
    if "ratchet_tiers" in p:
        patch["MOMENTUM_RATCHET_TIERS"] = str(p.get("ratchet_tiers") or "").strip()
    if "trail_pct" in p:
        _set("MOMENTUM_TRAIL_PCT", abs(float(p["trail_pct"])))
    if "trail_arm_pct" in p:
        _set("MOMENTUM_TRAIL_ARM_PCT", abs(float(p["trail_arm_pct"])))
    if "max_hold_bars" in p and p["max_hold_bars"] is not None:
        _set("MOMENTUM_MAX_HOLD_BARS", int(float(p["max_hold_bars"])))
    if "max_daily" in p:
        _set("MOMENTUM_MAX_DAILY", int(float(p["max_daily"])))
    if "min_price" in p:
        _set("MOMENTUM_MIN_PRICE", p["min_price"])
    if "max_daily_chg" in p:
        _set("MOMENTUM_MAX_DAILY_CHG", p["max_daily_chg"])
    if "high_chase_thr" in p and p["high_chase_thr"] is not None:
        _set("MOMENTUM_HIGH_CHASE_THR", float(p["high_chase_thr"]))
    if "max_loss_krw" in p:
        _set("MOMENTUM_MAX_LOSS_PER_TRADE_KRW", int(float(p["max_loss_krw"])))
    if "cooldown_min" in p and p["cooldown_min"] is not None:
        _set("MOMENTUM_COOLDOWN_SEC", int(float(p["cooldown_min"]) * 60))
    if "time_start_hm" in p:
        _set("MOMENTUM_TIME_START", int(float(p["time_start_hm"])))
    if "time_end_hm" in p:
        _set("MOMENTUM_TIME_END", int(float(p["time_end_hm"])))
    for js_k, env_k in (
        ("skip_hts_scan_dupes", "MOMENTUM_SKIP_HTS_SCAN_DUPES"),
        ("use_defense_filters", "MOMENTUM_USE_DEFENSE_FILTERS"),
        ("use_high_chase_filter", "MOMENTUM_USE_HIGH_CHASE_FILTER"),
        ("use_daily_range_filter", "MOMENTUM_USE_DAILY_RANGE_FILTER"),
        ("use_ema_filter", "MOMENTUM_USE_EMA_FILTER"),
        ("use_rsi_max_filter", "MOMENTUM_USE_RSI_MAX_FILTER"),
        ("pattern_breakout", "MOMENTUM_PATTERN_BREAKOUT"),
        ("pattern_pullback", "MOMENTUM_PATTERN_PULLBACK"),
    ):
        if js_k in p:
            _set(env_k, str(p[js_k]).lower())
    if "chase_lookback_min" in p:
        _set("MOMENTUM_CHASE_LOOKBACK_MIN", int(float(p["chase_lookback_min"])))
    if "pullback_lookback_min" in p:
        _set("MOMENTUM_PULLBACK_LOOKBACK_MIN", int(float(p["pullback_lookback_min"])))
    if "pullback_min_pct" in p:
        _set("MOMENTUM_PULLBACK_MIN_PCT", float(p["pullback_min_pct"]))
    if "pullback_max_pct" in p:
        _set("MOMENTUM_PULLBACK_MAX_PCT", float(p["pullback_max_pct"]))
    if "setup_vol_max_mult" in p:
        _set("MOMENTUM_SETUP_VOL_MAX_MULT", float(p["setup_vol_max_mult"]))
    if "setup_bear_bars_min" in p:
        _set("MOMENTUM_SETUP_BEAR_BARS_MIN", int(float(p["setup_bear_bars_min"])))
    if "ema_fast_period" in p:
        _set("MOMENTUM_EMA_FAST_PERIOD", int(float(p["ema_fast_period"])))
    if "ema_slow_period" in p:
        _set("MOMENTUM_EMA_SLOW_PERIOD", int(float(p["ema_slow_period"])))
    if "slot_money" in p:
        _set("MOMENTUM_SLOT_MONEY", int(float(p["slot_money"])))
    if "max_stocks" in p:
        _set("MOMENTUM_MAX_STOCKS", int(float(p["max_stocks"])))
    if "total_budget_krw" in p:
        _set("MOMENTUM_TOTAL_BUDGET_KRW", int(float(p["total_budget_krw"])))
    return patch
