"""
kis_trader/engine/program_filter.py — TRIGGER 진입 프로그램매매 필터 (키움 0w 캐시)
==================================================================================
실매 전용 — ``params['_program_ws']`` / ``_orderbook_ws`` / ``_whipsaw_ws`` 의
``get_program_snapshot`` 사용. 백테·WS 미구독 시 스킵.

모든 임계값 env/DB — 하드코딩 금지.
"""
from __future__ import annotations

from typing import Any, Dict, Optional, Tuple

from kis_trader.utils.env import get_env_bool, get_env_float, get_env_from_db, get_env_int
from kis_trader.ws.program_cache import ProgramSnapshot
from kis_trader.ws.trigger_eval_recorder import get_trigger_eval_recorder


def _strategy_prefix(strategy: str) -> str:
    s = (strategy or "").strip().upper()
    if s in ("BREAKOUT", "BO"):
        return "BREAKOUT"
    if s in ("MOMENTUM", "MOM"):
        return "MOMENTUM"
    if s in ("TAIL", "SHORT", "TAIL_CATCH"):
        return "TAIL"
    return s or "PROGRAM"


def program_filter_enabled(strategy: str) -> bool:
    prefix = _strategy_prefix(strategy)
    sk = f"{prefix}_PROGRAM_FILTER_ENABLED"
    raw = get_env_from_db(sk, "")
    if str(raw).strip() != "":
        return get_env_bool(sk, True)
    return get_env_bool("PROGRAM_FILTER_ENABLED", True)


def _program_filter_enabled_for_entry(params: Dict[str, Any], strategy: str) -> bool:
    """백테 폼값(임시 오버라이드) 우선 → 없으면 DB(실매와 동일).

    백테 탭 토글 시에만 ``params['_program_filter_enabled']`` 가 채워지며
    그 1회 백테에만 적용된다. 실매 루프는 이 키가 없어 DB 값만 읽는다.
    """
    ov = params.get("_program_filter_enabled")
    if ov is not None:
        return bool(ov)
    return program_filter_enabled(strategy)


def _f(common: str, specific: str, default: float) -> float:
    if str(get_env_from_db(specific, "")).strip():
        return get_env_float(specific, default)
    return get_env_float(common, default)


def _i(common: str, specific: str, default: int) -> int:
    sk = specific
    if str(get_env_from_db(sk, "")).strip():
        return get_env_int(sk, default)
    return get_env_int(common, default)


def _load_filter_params(strategy: str) -> Dict[str, Any]:
    pfx = _strategy_prefix(strategy)
    return {
        "min_net_buy_qty": _i("PROGRAM_MIN_NET_BUY_QTY", f"{pfx}_PROGRAM_MIN_NET_BUY_QTY", 0),
        "min_net_buy_amt": _i("PROGRAM_MIN_NET_BUY_AMT", f"{pfx}_PROGRAM_MIN_NET_BUY_AMT", 0),
        "max_sell_buy_qty_ratio": _f(
            "PROGRAM_MAX_SELL_BUY_QTY_RATIO",
            f"{pfx}_PROGRAM_MAX_SELL_BUY_QTY_RATIO",
            999.0,
        ),
        "min_net_delta_qty": _i(
            "PROGRAM_MIN_NET_DELTA_QTY",
            f"{pfx}_PROGRAM_MIN_NET_DELTA_QTY",
            0,
        ),
        "max_age_sec": _f("PROGRAM_SNAPSHOT_MAX_AGE_SEC", f"{pfx}_PROGRAM_SNAPSHOT_MAX_AGE_SEC", 30.0),
    }


def _resolve_ws(params: Dict[str, Any]):
    return (
        params.get("_program_ws")
        or params.get("_orderbook_ws")
        or params.get("_whipsaw_ws")
    )


def _resolve_code(params: Dict[str, Any]) -> str:
    return str(
        params.get("_program_code")
        or params.get("_orderbook_code")
        or params.get("_whipsaw_code")
        or ""
    ).strip()


def _fetch_snapshot(params: Dict[str, Any], max_age_sec: float) -> Optional[ProgramSnapshot]:
    bt = params.get("_backtest_program_snapshot")
    if isinstance(bt, ProgramSnapshot):
        return bt

    ws = _resolve_ws(params)
    code = _resolve_code(params)
    if not ws or not code:
        return None
    getter = getattr(ws, "get_program_snapshot", None)
    if not callable(getter):
        return None
    try:
        snap = getter(code, max_age_sec=max_age_sec)
    except Exception:
        return None
    if snap is None:
        return None
    if isinstance(snap, ProgramSnapshot):
        return snap
    return None


def _persist_program_eval(
    params: Dict[str, Any],
    strategy: str,
    snap: ProgramSnapshot,
    reject_code: Optional[str],
    reject_msg: Optional[str],
    current_price: float,
) -> None:
    """실매 필터 판정 직후 RAM 스냅샷을 DB에 저장 (백테 재생용)."""
    if params.get("_backtest_program_snapshot") is not None:
        return
    if params.get("_skip_trigger_eval_persist"):
        return
    rec = get_trigger_eval_recorder()
    if rec is None:
        return
    code = _resolve_code(params) or (snap.code or "")
    rec.on_program_eval(
        strategy=strategy,
        code=code,
        snap=snap,
        reject_code=reject_code,
        reject_msg=reject_msg,
        current_price=current_price,
    )


def program_reject_for_entry(
    params: Dict[str, Any],
    strategy: str,
    *,
    current_price: float = 0.0,
) -> Tuple[Optional[str], Optional[str]]:
    """
    TRIGGER 통과 직전 프로그램매매 검사.
    Returns: (reject_code, message) — 통과 시 (None, None)
    """
    del current_price  # 향후 가격 대비 수급 비율 필터용 예약
    if not _program_filter_enabled_for_entry(params, strategy):
        return (None, None)

    cfg = _load_filter_params(strategy)
    max_age = float(cfg["max_age_sec"])
    snap = _fetch_snapshot(params, max_age_sec=max_age)
    if snap is None:
        return (None, None)

    min_net_qty = int(cfg["min_net_buy_qty"])
    if snap.net_qty < min_net_qty:
        rej = (
            "탈락-프로그램매도세",
            f"순매수수량 {snap.net_qty:,} < 최소 {min_net_qty:,}",
        )
        _persist_program_eval(params, strategy, snap, rej[0], rej[1], current_price)
        return rej

    min_net_amt = int(cfg["min_net_buy_amt"])
    if min_net_amt > 0 and snap.net_amt < min_net_amt:
        rej = (
            "탈락-프로그램순매수부족",
            f"순매수대금 {snap.net_amt:,} < 최소 {min_net_amt:,}",
        )
        _persist_program_eval(params, strategy, snap, rej[0], rej[1], current_price)
        return rej

    max_ratio = float(cfg["max_sell_buy_qty_ratio"])
    if max_ratio < 900.0 and snap.buy_qty > 0:
        ratio = snap.sell_buy_qty_ratio()
        if ratio > max_ratio:
            rej = (
                "탈락-프로그램매도우위",
                f"매도/매수수량비 {ratio:.2f} > {max_ratio:.2f}",
            )
            _persist_program_eval(params, strategy, snap, rej[0], rej[1], current_price)
            return rej

    min_delta = int(cfg["min_net_delta_qty"])
    if min_delta > 0 and snap.delta_net_qty < min_delta:
        rej = (
            "탈락-프로그램유입부족",
            f"순매수증가 {snap.delta_net_qty:,} < 최소 {min_delta:,}",
        )
        _persist_program_eval(params, strategy, snap, rej[0], rej[1], current_price)
        return rej

    _persist_program_eval(params, strategy, snap, None, None, current_price)
    return (None, None)
