#!/usr/bin/env python3
"""SCALP 2026-07-16 실매↔웹BT 진입 괴리 진단 (adhoc)."""
from __future__ import annotations

from collections import defaultdict
from typing import Any, Dict, List, Optional, Tuple

from database import TradeDB
from kis_trader.backtest.scalping_backtest_common import resolve_scalp_universe
from kis_trader.engine.scalping_engine import (
    _apply_buy_state_filters,
    _eval_scalp_buy_at_index,
    get_scalping_defaults_from_db,
)


def _z(code: Any) -> str:
    s = str(code or "").strip()
    return s.zfill(6) if s.isdigit() else s


def _hm_from_buy(buy_date: str) -> Tuple[str, str]:
    """'2026-07-16 09:39:03' → day=20260716, entry_key≈202607160939"""
    raw = str(buy_date or "").strip().replace("-", "").replace(":", "").replace(" ", "")
    day = raw[:8]
    hm = raw[8:12] if len(raw) >= 12 else ""
    return day, day + hm


def load_candles(db: TradeDB, code: str, day: str) -> List[Dict]:
    rows = db.conn.execute(
        """SELECT candle_time, open, high, low, close, volume, is_confirmed
           FROM ws_candles
           WHERE code=%s AND timeframe=1 AND candle_time LIKE %s
           ORDER BY candle_time""",
        (code, day + "%"),
    ).fetchall()
    out = []
    for r in rows:
        d = dict(r)
        d["candle_time"] = str(d["candle_time"])
        out.append(d)
    return out


def find_index(candles: List[Dict], key12: str) -> Optional[int]:
    k = str(key12)[:12]
    for i, c in enumerate(candles):
        if str(c["candle_time"])[:12] == k:
            return i
    return None


def nearest_index(candles: List[Dict], key12: str) -> Optional[int]:
    k = str(key12)[:12]
    best = None
    best_d = 10**9
    for i, c in enumerate(candles):
        ct = str(c["candle_time"])[:12]
        if len(ct) < 12:
            continue
        try:
            d = abs(int(ct) - int(k))
        except ValueError:
            continue
        if d < best_d:
            best_d = d
            best = i
    return best


def eval_at(candles: List[Dict], i: int, params: Dict) -> Tuple[str, str]:
    if i is None or i < 1:
        return "no_idx", "봉인덱스 없음"
    state = {"daily_cnt": 0, "last_exit_dt": None}
    st = _apply_buy_state_filters(candles, i, params, state)
    if st[2] is None:
        return "state", str(st[0] or st[1] or "state_reject")
    rej, msg, sig = _eval_scalp_buy_at_index(candles, i, params)
    if rej:
        return str(rej), str(msg or "")
    if not sig:
        return "no_sig", "신호없음"
    rsi = sig.get("rsi")
    return "PASS", "rsi=%s mode=%s" % (rsi, sig.get("entry_mode"))


def slot_has(univ: Dict[str, List[str]], slot: str, code: str) -> bool:
    if not univ:
        return False
    return code in (univ.get(slot) or [])


def first_slots(univ: Dict[str, List[str]], code: str, day: str, limit: int = 8) -> List[str]:
    out = []
    for sk in sorted(univ.keys()):
        if not sk.startswith(day):
            continue
        if code in (univ.get(sk) or []):
            out.append(sk)
            if len(out) >= limit:
                break
    return out


def main() -> None:
    db = TradeDB()
    live_rows = db.conn.execute(
        """SELECT code, name, buy_date, sell_date, buy_price, sell_reason, realized_pnl
           FROM trade_history WHERE strategy=%s AND buy_date LIKE %s
           ORDER BY buy_date""",
        ("SCALP", "2026-07-16%"),
    ).fetchall()
    active_rows = db.conn.execute(
        """SELECT code, name, buy_date, avg_buy_price
           FROM active_trades WHERE strategy=%s AND buy_date LIKE %s""",
        ("SCALP", "2026-07-16%"),
    ).fetchall()

    # 사용자 웹 BT 9건 (스크린샷)
    bt_user = [
        ("001130", "2026-07-16 14:55:00"),
        ("067830", "2026-07-16 14:25:00"),
        ("035000", "2026-07-16 09:55:00"),
        ("047770", "2026-07-16 14:27:00"),
        ("226400", "2026-07-16 12:36:00"),
        ("330350", "2026-07-16 12:42:00"),
        ("460930", "2026-07-16 09:21:00"),
        ("007540", "2026-07-16 09:36:00"),
        ("439090", "2026-07-16 09:52:00"),
    ]
    bt_codes = {_z(c) for c, _ in bt_user}
    live_codes = {_z(r["code"]) for r in list(live_rows) + list(active_rows)}

    params = get_scalping_defaults_from_db()
    params.update({
        "rsi_period": 3,
        "rsi_oversold": 23.0,
        "rsi_overbought": 75.0,
        "sl_pct": 0.035,
        "tp_pct": 0.03,
        "tp_max_pct": 0.04,
        "drop_rate": 0.01,
        "vol_mult": 1.5,
        "use_defense_filters": False,
        "use_macd_cross": False,
        "skip_hts_scan_dupes": False,
        "require_reversal_candle": False,
        "min_price": 6000.0,
        "high_chase_thr": 0.99,
        "max_daily_chg": 50.0,
        "cooldown_min": 5,
        "time_start_hm": 900,
        "time_end_hm": 1530,
        "max_daily": 100,
    })

    univ, usrc, nslots, _ = resolve_scalp_universe(
        "2026-07-16", "2026-07-16", use_saved_history=True, strategy_id="SCALP",
    )
    univ = univ or {}
    print("UNIVERSE", usrc, "slots", nslots)

    print("\n" + "=" * 72)
    print("A) 실매 ONLY — 왜 BT가 못 샀나 (매수시각 기준 신호봉=진입직전봉)")
    print("=" * 72)

    for r in live_rows:
        code = _z(r["code"])
        if code in bt_codes:
            tag = "BOTH"
        else:
            tag = "LIVE_ONLY"
        day, entry_key = _hm_from_buy(r["buy_date"])
        candles = load_candles(db, code, day)
        # ALIGN: 진입봉=entry_key, 신호봉=직전 확정봉
        entry_i = find_index(candles, entry_key)
        if entry_i is None:
            entry_i = nearest_index(candles, entry_key)
        signal_i = (entry_i - 1) if entry_i is not None and entry_i >= 1 else None

        slots = first_slots(univ, code, day, 5)
        in_entry_slot = slot_has(univ, entry_key, code) if entry_key else False
        sig_key = str(candles[signal_i]["candle_time"])[:12] if signal_i is not None else ""
        in_sig_slot = slot_has(univ, sig_key, code) if sig_key else False

        status, detail = ("no_candle", "분봉0")
        if signal_i is not None:
            status, detail = eval_at(candles, signal_i, params)
        # vol detail if reject
        vol_info = ""
        if signal_i is not None and candles:
            c = candles[signal_i]
            vols = [float(x.get("volume") or 0) for x in candles]
            win = max(1, min(20, signal_i))
            avg = sum(vols[signal_i - win : signal_i]) / win if win else 0
            vol = vols[signal_i]
            vol_info = "vol=%.0f avg20=%.0f need>=%.0f" % (
                vol, avg, avg * float(params["vol_mult"]),
            )

        print(
            f"\n[{tag}] {code} {r['name']} live_buy={r['buy_date']} @{r['buy_price']}"
        )
        print(f"  candles={len(candles)} entry_key={entry_key} signal_i={signal_i} sig_key={sig_key}")
        print(f"  universe: in_entry_slot={in_entry_slot} in_sig_slot={in_sig_slot} first_slots={slots}")
        print(f"  TRIGGER@signal: {status} | {detail} | {vol_info}")

        # also scan morning for first PASS in BT conditions
        first_pass = None
        for i in range(1, len(candles)):
            st, det = eval_at(candles, i, params)
            if st == "PASS":
                ck = str(candles[i]["candle_time"])[:12]
                if slot_has(univ, ck, code) or not univ:
                    first_pass = (ck, det)
                    break
        print(f"  first PASS+univ day: {first_pass}")

    print("\n" + "=" * 72)
    print("B) BT ONLY — 실매는 왜 안 샀나 (BT 매수시각 기준)")
    print("=" * 72)
    for code, buy_t in bt_user:
        code = _z(code)
        if code in live_codes:
            continue
        day, entry_key = _hm_from_buy(buy_t)
        candles = load_candles(db, code, day)
        entry_i = find_index(candles, entry_key)
        signal_i = (entry_i - 1) if entry_i is not None and entry_i >= 1 else None
        sig_key = str(candles[signal_i]["candle_time"])[:12] if signal_i is not None else ""
        status, detail = eval_at(candles, signal_i, params) if signal_i is not None else ("no", "")
        slots = first_slots(univ, code, day, 5)
        # history presence count
        n_hist = db.conn.execute(
            "SELECT COUNT(*) n FROM target_candidates_history WHERE strategy_id=%s AND code=%s AND slot_key LIKE %s",
            ("SCALP", code, day + "%"),
        ).fetchone()["n"]
        print(f"\n[BT_ONLY] {code} bt_buy={buy_t}")
        print(f"  hist_rows={n_hist} first_slots={slots}")
        print(f"  TRIGGER@BT_signal {sig_key}: {status} | {detail}")
        print(f"  in_sig_slot={slot_has(univ, sig_key, code)} in_entry_slot={slot_has(univ, entry_key, code)}")

    print("\n" + "=" * 72)
    print("C) BOTH — 시각 차이")
    print("=" * 72)
    live_by = {_z(r["code"]): r for r in live_rows}
    for code, buy_t in bt_user:
        code = _z(code)
        if code not in live_by:
            continue
        lr = live_by[code]
        print(f"  {code}: LIVE {lr['buy_date']} @{lr['buy_price']} | BT {buy_t}")

    # vol_mult ON/OFF sensitivity for LIVE_ONLY
    print("\n" + "=" * 72)
    print("D) LIVE_ONLY — vol_mult=0 이면 PASS 되나?")
    print("=" * 72)
    p0 = dict(params)
    p0["vol_mult"] = 0.0
    for r in live_rows:
        code = _z(r["code"])
        if code in bt_codes:
            continue
        day, entry_key = _hm_from_buy(r["buy_date"])
        candles = load_candles(db, code, day)
        entry_i = find_index(candles, entry_key) or nearest_index(candles, entry_key)
        signal_i = (entry_i - 1) if entry_i and entry_i >= 1 else None
        s1, d1 = eval_at(candles, signal_i, params) if signal_i is not None else ("?", "")
        s0, d0 = eval_at(candles, signal_i, p0) if signal_i is not None else ("?", "")
        print(f"  {code}: vol1.5={s1}({d1[:40]}) | vol0={s0}({d0[:40]})")

    # min_price with defense OFF should not block — confirm
    print("\n" + "=" * 72)
    print("E) 슬롯 경쟁 가설 — 실매 매수 시각에 BT 후보가 몇 개?")
    print("=" * 72)
    # for each live buy minute, count how many codes PASS in that signal slot among univ
    for r in live_rows[:5]:
        day, entry_key = _hm_from_buy(r["buy_date"])
        # signal approx entry-1min
        try:
            sig_num = int(entry_key) - 1
            sig_key = str(sig_num)
        except Exception:
            sig_key = entry_key
        # fix minute borrow
        if entry_key.endswith("00"):
            # 0900 -> 0859 not valid; use find
            pass
        hh = int(entry_key[8:10])
        mm = int(entry_key[10:12])
        if mm == 0:
            hh -= 1
            mm = 59
        else:
            mm -= 1
        sig_key = "%s%02d%02d" % (day, hh, mm)
        codes_in = list(univ.get(sig_key) or [])
        passes = []
        for c in codes_in[:80]:
            candles = load_candles(db, _z(c), day)
            si = find_index(candles, sig_key)
            if si is None:
                continue
            st, det = eval_at(candles, si, params)
            if st == "PASS":
                passes.append((_z(c), det))
        print(
            f"  live={_z(r['code'])} @{r['buy_date']} sig={sig_key} "
            f"univ={len(codes_in)} PASS={len(passes)} sample={passes[:6]}"
        )

    db.close()


if __name__ == "__main__":
    main()
