"""
kis_trader/strategies/momentum.py — 모멘텀 전략 (1분봉 추세추격)
================================================================
스캘핑 reversal(SCALP)과 완전 분리 — ``momentum_engine`` 전용.

[SCAN vs TRIGGER]
- SCAN: HTS/KIS ``scalp`` 조건검색 → target_candidates_history
- TRIGGER: 양봉, RSI 강세, 거래량 spike, 방어필터 (고점추격·급등·시가위치)

[엔진]
- 진입: ``momentum_engine.check_buy_signal_momentum_live``
- 청산: ``momentum_engine.check_sell_signal_momentum_live`` (어깨·트레일 선행, tp_max 상한 익절 마지막)
- 백테: ``momentum_engine.run_momentum_backtest`` / ``check_sell_signal_momentum_backtest_bar``
"""
from __future__ import annotations

from datetime import datetime as dt
from typing import Dict, List, Optional

from ..engine import momentum_engine as me
from ..utils.env import get_env_bool, get_env_float, get_env_from_db, get_env_int
from ..utils.position_sizing import invest_qty_for_price
from .base import BaseStrategy, is_live_eod_now


class MomentumStrategy(BaseStrategy):
    """1분봉 단기 추세추격 — SCALP reversal 과 청산·진입 모두 분리."""

    strategy_id = "MOMENTUM"
    loop_min_sleep = 1.0
    loop_max_sleep = 2.0

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.candle_tf = 1
        self._engine_params: Optional[Dict] = None
        self.reload_config()

    def reload_config(self) -> None:
        """env_config → momentum_engine params."""
        try:
            base = me.get_momentum_defaults_from_db()
            base.update({
                "fee_rate": get_env_float("FEE_RATE_PCT", 0.015) / 100,
                "sell_tax": get_env_float("SELL_TAX_RATE_PCT", 0.18) / 100,
            })
            self._engine_params = base
            self.min_price = float(base.get("min_price", 1000.0))
            self.stop_loss_pct = -abs(float(base.get("sl_pct", 0.015)))
            self.take_profit_pct = abs(float(base.get("tp_pct", 0.025)))
            self.tp_max_pct = abs(float(base.get("tp_max_pct", 0.02)))
            self.slot_money = int(base.get("slot_money", 3_000_000))
            self.mom_rsi_min = float(base.get("mom_rsi_min", 50.0))
            self.mom_rsi_max = float(base.get("mom_rsi_max", 80.0))
            self.max_daily = int(base.get("max_daily", 5))
            self.eod_enabled = get_env_bool("MOMENTUM_EOD_ENABLED", True)
            self.eod_hm = get_env_from_db("MOMENTUM_EOD_HM", "15:25")
        except Exception as e:
            self.logger.debug("momentum_engine defaults 조회 실패: %s", e)
            self._engine_params = {}

    def _candidate_filter(self, candidate: Dict) -> bool:
        return bool(candidate.get("scalp_on", True))

    def check_buy(self, code: str, name: str) -> Optional[Dict]:
        try:
            if get_env_bool("FORCE_BUY_TEST", False):
                return self._force_buy_test(code, name)

            candles_raw = self.ws.get_candles(code, self.candle_tf, n=50)
            if len(candles_raw) < 5:
                return None
            candles = [self._norm_candle(c) for c in candles_raw]

            today = dt.now().strftime("%Y%m%d")
            last_exit_dt = None
            if code in self.recently_sold:
                try:
                    last_exit_dt = dt.fromtimestamp(self.recently_sold[code])
                    if last_exit_dt.strftime("%Y%m%d") != today:
                        last_exit_dt = None
                except Exception:
                    pass
            try:
                today_trades = self.db.get_trades_by_date(today)
                daily_cnt = len([
                    t for t in today_trades
                    if t.get("code") == code and str(t.get("strategy", "")).upper() == "MOMENTUM"
                ])
            except Exception:
                daily_cnt = 0
            state = {"last_exit_dt": last_exit_dt, "daily_cnt": daily_cnt}

            params = dict(self._engine_params or {})
            params["_whipsaw_ws"] = self.ws
            params["_whipsaw_code"] = code
            params["_orderbook_ws"] = self.ws
            params["_orderbook_code"] = code
            params["_program_ws"] = self.ws
            params["_program_code"] = code
            params["slot_money"] = self.slot_money
            reject, msg, sig = me.check_buy_signal_momentum_live(candles, params, state)
            if reject:
                self.logger.info("🔍 [%s] %s %s: %s", reject, name, code, msg or "")
                return None
            if not sig:
                return None

            align_on = get_env_bool("MOMENTUM_LIVE_BACKTEST_ALIGN", True)
            entry_open = float(sig.get("entry_price", 0) or 0)
            if align_on and entry_open > 0:
                curr_price = entry_open
            else:
                latest = candles[-1]
                curr_price = float(latest["close"])
                wsd = self.ws.get_price(code)
                if wsd:
                    try:
                        curr_price = abs(
                            float(str(wsd.get("stck_prpr", curr_price)).replace(",", ""))
                        ) or curr_price
                    except Exception:
                        pass
            if curr_price <= 0 or curr_price < self.min_price:
                return None

            hard_cap = get_env_int("MOMENTUM_MAX_BUY_AMOUNT", 0) or get_env_int(
                "MAX_BUY_AMOUNT_PER_STOCK", 0,
            )
            qty, rej = self._resolve_buy_qty_live(
                curr_price, hard_cap=hard_cap,
            )
            if rej:
                self.logger.info(
                    "🔍 [탈락-%s] %s(%s) price=%.0f",
                    rej, name, code, curr_price,
                )
                return None

            eff_tp = me.resolve_effective_tp_pct(self.take_profit_pct, self.tp_max_pct)
            stop_price = curr_price * (1 + self.stop_loss_pct)
            target_price = curr_price * (1 + eff_tp)

            self.logger.info(
                "🎯 [MOMENTUM 시그널] %s(%s) price=%.0f qty=%d RSI=%.1f",
                name, code, curr_price, qty, sig.get("rsi", 0),
            )
            return {
                "code": code,
                "name": name,
                "price": curr_price,
                "qty": qty,
                "stop_price": stop_price,
                "target_price": target_price,
                "atr_entry": 0.0,
                "size_class": "",
                "entry_features": {"rsi": sig.get("rsi", 0)},
            }
        except Exception as e:
            self.logger.info("🔍 [탈락-예외] %s %s: %s", name, code, e)
            return None

    def _force_buy_test(self, code: str, name: str) -> Optional[Dict]:
        wsd = self.ws.get_price(code)
        px = 0.0
        if wsd:
            try:
                px = abs(float(str(wsd.get("stck_prpr", 0)).replace(",", "")))
            except Exception:
                px = 0.0
        if px <= 0:
            pd_ = self.client.inquire_price(code)
            if pd_:
                try:
                    px = abs(float(str(pd_.get("stck_prpr", 0)).replace(",", "")))
                except Exception:
                    px = 0.0
        if px <= 0:
            return None
        qty = invest_qty_for_price(px, float(self.slot_money))
        if qty < 1:
            return None
        eff_tp = me.resolve_effective_tp_pct(self.take_profit_pct, self.tp_max_pct)
        return {
            "code": code,
            "name": name,
            "price": px,
            "qty": qty,
            "stop_price": px * (1 + self.stop_loss_pct),
            "target_price": px * (1 + eff_tp),
            "atr_entry": 0.0,
            "size_class": "",
            "entry_features": {},
        }

    def check_sell_signals(self) -> List[Dict]:
        if not self.holdings:
            return []

        signals: List[Dict] = []
        now = dt.now()
        is_eod = is_live_eod_now(
            getattr(self, "eod_enabled", True),
            getattr(self, "eod_hm", "15:25"),
            now,
            default_hm="15:25",
        )
        params = dict(self._engine_params or me.get_momentum_defaults_from_db())

        for code, holding in list(self.holdings.items()):
            try:
                name = holding.get("name", code)
                buy_price = float(holding.get("buy_price", 0))
                qty = int(holding.get("qty", 0))
                stop = float(holding.get("stop_price", 0))
                target = float(holding.get("target_price", 0))
                max_price = float(holding.get("max_price", buy_price))

                if qty <= 0 or buy_price <= 0:
                    continue

                current_price = 0.0
                wsd = self.ws.get_price(code)
                if wsd:
                    try:
                        current_price = abs(
                            float(str(wsd.get("stck_prpr", 0)).replace(",", ""))
                        )
                    except Exception:
                        current_price = 0.0
                if current_price <= 0:
                    pd_ = self.client.inquire_price(code)
                    if pd_:
                        try:
                            current_price = abs(
                                float(str(pd_.get("stck_prpr", 0)).replace(",", ""))
                            )
                        except Exception:
                            current_price = 0.0
                if current_price <= 0:
                    continue

                if current_price > max_price:
                    max_price = current_price
                    holding["max_price"] = max_price

                profit_pct = (current_price - buy_price) / buy_price if buy_price > 0 else 0
                position = {
                    "entry_price": buy_price,
                    "entry_time": holding.get("buy_time", ""),
                    "qty": qty,
                    "stop": stop,
                    "target": target,
                    "max_price": max_price,
                }
                candle = {
                    "high": max_price,
                    "low": current_price,
                    "close": current_price,
                    "candle_time": now.strftime("%Y%m%d%H%M"),
                }

                res = me.check_sell_signal_momentum_live(position, candle, params, is_eod=is_eod)
                if not res:
                    continue
                reason, exit_price = res
                signals.append({
                    "code": code,
                    "name": name,
                    "current_price": current_price,
                    "price": exit_price,
                    "qty": qty,
                    "buy_price": buy_price,
                    "profit_pct": profit_pct,
                    "reason": reason,
                })
            except Exception as e:
                self.logger.error("매도 시그널 체크 오류(%s): %s", code, e)

        return signals

    def _norm_candle(self, c: dict) -> dict:
        ct = c.get("candle_time") or c.get("candle_time_str", "")
        if isinstance(ct, str) and len(ct) == 19 and " " in ct:
            ct = ct.replace("-", "").replace(" ", "").replace(":", "")[:12]
        return {
            "candle_time": ct,
            "open": float(c.get("open", 0)),
            "high": float(c.get("high", 0)),
            "low": float(c.get("low", 0)),
            "close": float(c.get("close", 0)),
            "volume": float(c.get("volume", 0)),
            "is_confirmed": c.get("is_confirmed", 1),
        }
