"""
kis_trader/engine/orderbook_filter.py — TRIGGER 진입 호가 필터 (키움 0D 캐시)
============================================================================
실매 전용 — ``params['_orderbook_ws']`` / ``_whipsaw_ws`` 의 ``get_orderbook`` 사용.
백테는 호가 없으면 스킵(OFF) 또는 env 로 비활성.

필터(주문 차단)와 수집(ws_orderbook 저장)은 분리:
  - ``{전략}_ORDERBOOK_FILTER_ENABLED`` — 실매 탈락 적용 (글로벌 ORDERBOOK_* 폐기)
  - ``WS_ORDERBOOK_COLLECT_ENABLED`` — TRIGGER 판정 스냅 저장 (필터 OFF여도 가능)

모든 임계값 env/DB — 하드코딩 금지 (기본값은 orderbook_env 상수).
"""
from __future__ import annotations

from typing import Any, Dict, Optional, Tuple

from kis_trader.utils.env import get_env_int
from kis_trader.ws.orderbook_cache import OrderbookSnapshot
from kis_trader.ws.trigger_eval_recorder import get_trigger_eval_recorder

from .orderbook_env import (
    load_orderbook_threshold_cfg,
    orderbook_filter_enabled,
    orderbook_strategy_prefix,
)
from .trigger_eval_collect import orderbook_collect_enabled

LOG_BACKFILL_SOURCE = "log_backfill"


def _strategy_prefix(strategy: str) -> str:
    return orderbook_strategy_prefix(strategy)


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

    백테 탭에서 켜고/끄고 돌릴 때만 ``params['_orderbook_filter_enabled']`` 가
    채워지며(그 1회 백테에만 적용), 실매 루프는 이 키가 없어 DB 값만 읽는다.
    """
    ov = params.get("_orderbook_filter_enabled")
    if ov is not None:
        return bool(ov)
    return orderbook_filter_enabled(strategy)


def _load_filter_params(strategy: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    return load_orderbook_threshold_cfg(strategy, params)


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


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


def _fetch_snapshot(params: Dict[str, Any]) -> Optional[OrderbookSnapshot]:
    bt = params.get("_backtest_orderbook_snapshot")
    if isinstance(bt, OrderbookSnapshot):
        return bt

    ws = _resolve_ws(params)
    code = _resolve_code(params)
    if not ws or not code:
        return None
    getter = getattr(ws, "get_orderbook_snapshot", None)
    if callable(getter):
        try:
            snap = getter(code)
            if snap is not None:
                return snap
        except Exception:
            pass
    getter2 = getattr(ws, "get_orderbook", None)
    if not callable(getter2):
        return None
    try:
        raw = getter2(code)
    except Exception:
        return None
    if raw is None:
        return None
    if isinstance(raw, OrderbookSnapshot):
        return raw
    return None


def _estimate_entry_qty(params: Dict[str, Any], price: float) -> int:
    slot = float(params.get("slot_money") or params.get("_slot_money") or 0)
    if slot <= 0:
        slot = float(get_env_int("SLOT_MONEY_DEFAULT", 200_000))
    if price <= 0:
        return 0
    return max(1, int(slot / price))


def _persist_orderbook_eval(
    params: Dict[str, Any],
    strategy: str,
    snap: OrderbookSnapshot,
    reject_code: Optional[str],
    reject_msg: Optional[str],
    current_price: float,
) -> None:
    """실매 TRIGGER 판정 직후 RAM 스냅샷을 DB에 저장 (백테 재생용)."""
    if params.get("_backtest_orderbook_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_orderbook_eval(
        strategy=strategy,
        code=code,
        snap=snap,
        reject_code=reject_code,
        reject_msg=reject_msg,
        current_price=current_price,
    )


def _evaluate_orderbook_verdict(
    snap: OrderbookSnapshot,
    strategy: str,
    params: Dict[str, Any],
    *,
    current_price: float,
    resistance: Optional[float] = None,
) -> Tuple[Optional[str], Optional[str]]:
    """호가 규칙만 평가 — (reject_code, msg) 또는 통과 (None, None)."""
    cfg = _load_filter_params(strategy, params)
    pfx = _strategy_prefix(strategy)

    spread = snap.spread_pct()
    max_spread = float(cfg["max_spread_pct"])
    if spread > max_spread:
        return (
            "탈락-호가스프레드",
            f"스프레드 {spread:.2f}% > {max_spread:.2f}%",
        )

    bid_lv = int(cfg["entry_bid_levels"])
    bid_sum = snap.bid_qty_sum(bid_lv)
    ask_sum = snap.ask_qty_sum(bid_lv)
    if ask_sum > 0:
        ratio = bid_sum / ask_sum
        min_ratio = float(cfg["min_bid_ask_ratio"])
        if ratio < min_ratio:
            return (
                "탈락-호가수급",
                f"매수/매도잔량({bid_lv}호가) {ratio:.2f} < {min_ratio:.2f}",
            )

    qty_need = _estimate_entry_qty(params, current_price)
    depth_mult = float(cfg["entry_bid_depth_mult"])
    if qty_need > 0 and bid_sum < int(qty_need * depth_mult):
        return (
            "탈락-매수호가얇음",
            f"매수{bid_lv}호가 합 {bid_sum}주 < 필요 {int(qty_need * depth_mult)}주",
        )

    ask_max_mult = float(cfg["entry_ask_max_mult"])
    if qty_need > 0 and ask_sum > int(qty_need * ask_max_mult):
        return (
            "탈락-매도벽",
            f"매도{bid_lv}호가 합 {ask_sum}주 > 허용 {int(qty_need * ask_max_mult)}주",
        )

    if pfx == "BREAKOUT" and resistance and resistance > 0:
        wall_max = int(cfg["breakout_ask_wall_max_qty"])
        ask_above = sum(
            lv.qty for lv in snap.asks
            if lv.price > 0 and lv.price <= resistance * 1.002
        )
        if ask_above > wall_max:
            return (
                "탈락-돌파매도벽",
                f"저항 {resistance:,.0f} 근처 매도잔량 {ask_above} > {wall_max}",
            )

    if pfx == "TAIL" and snap.total_bid_qty > 0 and snap.total_ask_qty > 0:
        tail_ratio = snap.total_bid_qty / snap.total_ask_qty
        min_tail = float(cfg["min_bid_ask_ratio"])
        if tail_ratio < min_tail:
            return (
                "탈락-꼬리지지부족",
                f"총매수/총매도 {tail_ratio:.2f} < {min_tail:.2f}",
            )

    return (None, None)


def orderbook_reject_for_entry(
    params: Dict[str, Any],
    strategy: str,
    *,
    current_price: float,
    resistance: Optional[float] = None,
) -> Tuple[Optional[str], Optional[str]]:
    """
    TRIGGER 통과 직전 호가 검사.
    Returns: (reject_code, message) — 통과 시 (None, None)

    필터 OFF + 수집 ON 이면 규칙은 평가·DB 저장만 하고 주문은 막지 않음.
    """
    filter_on = _orderbook_filter_enabled_for_entry(params, strategy)
    collect_on = orderbook_collect_enabled(params, strategy)
    if not filter_on and not collect_on:
        return (None, None)

    # log_backfill 판정 재생 — 호가 본체 없이 실매와 동일 pass/reject (필터 ON일 때만)
    log_verdict = params.get("_backtest_log_orderbook_verdict")
    bt_snap = params.get("_backtest_orderbook_snapshot")
    if filter_on and log_verdict is not None and (
        bt_snap is None or str(getattr(bt_snap, "source", "") or "") == LOG_BACKFILL_SOURCE
    ):
        rej_code, rej_msg = log_verdict
        if rej_code:
            return (rej_code, rej_msg or "")
        return (None, None)

    snap = _fetch_snapshot(params)
    if snap is None:
        # 실매 WS 미구독·만료 — 필터·수집 모두 스킵 (REST 부하 없음)
        return (None, None)

    verdict = _evaluate_orderbook_verdict(
        snap, strategy, params,
        current_price=current_price,
        resistance=resistance,
    )
    rej_code, rej_msg = verdict

    if collect_on:
        _persist_orderbook_eval(
            params, strategy, snap, rej_code, rej_msg, current_price,
        )

    if filter_on and rej_code:
        return verdict
    return (None, None)
