#!/usr/bin/env python3
"""Phase A: live_trader dry-run·sim_causal_hybrid(06) 정합·한도 점검."""

from __future__ import annotations

import runpy
from datetime import datetime
from pathlib import Path

runpy.run_path(str(Path(__file__).resolve().parent / "_bootstrap.py"))

from config import (  # noqa: E402
    CHART_LOOKBACK_DAYS,
    GT_INITIAL_CASH_KRW,
    GT_SIGNAL_CAUSAL,
    LIVE_COOLDOWN_MIN,
    LIVE_DAILY_KRW_MAX,
    LIVE_DAILY_LOSS_LIMIT_KRW,
    LIVE_MAX_TRADES_PER_DAY,
    LIVE_ORDER_KRW,
    LIVE_TRADING_ENABLED,
    MATCH_LIVE_CACHE_SEC,
    MATCH_PRIMARY_INTERVAL,
    MONITOR_ALERT_KRW_AMOUNT,
    MONITOR_LOOP_SLEEP_SEC,
    SIM_PRIMARY_SIZING,
    SYMBOL,
    TRADING_FEE_RATE,
)
from deepcoin.data.mtf_bb import load_frames_from_db
from deepcoin.ground_truth.hybrid_dd_calibrate import load_hybrid_dd_params
from deepcoin.matching.live_eval import evaluate_live_rules
from deepcoin.ground_truth.causal_gt_hybrid import _close_series_from_df, hybrid_tier_scale
from deepcoin.ground_truth.gt_model import leg_entry_weights
from deepcoin.matching.position_sizing import compute_buy_amount_krw
from deepcoin.matching.load_rules import load_monitor_rules
from deepcoin.matching.live_sizing import LivePositionState, live_sizing_enabled
from deepcoin.ops.hybrid_sim_execution import replay_paper_portfolio
from deepcoin.ops.live_trader import LiveTrader
from deepcoin.ops.monitor import Monitor
from deepcoin.ops.paper_portfolio import PaperPortfolio
from deepcoin.matching.position_sizing import load_ev_wf_approved_rule_ids


def _plan_with_dd(
    cash: float,
    qty: float,
    price: float,
    dd_pct: float,
    *,
    enhanced: bool,
    completed_leg_ret: dict[int, float] | None = None,
    leg_id: int = 1,
) -> float:
    """drawdown %를 고정한 tier 매수 원화 (검증용)."""
    weights = leg_entry_weights([price])
    trade: dict = {"leg_id": leg_id, "drawdown_pct": dd_pct}
    dd_params = load_hybrid_dd_params()
    scale = hybrid_tier_scale(
        trade,
        completed_leg_ret=completed_leg_ret or {},
        enhanced=enhanced,
        dd_large_pct=dd_params.get("dd_large_pct"),
        dd_medium_pct=dd_params.get("dd_medium_pct"),
    )
    return compute_buy_amount_krw(
        cash,
        qty,
        price,
        weights[0],
        weights[0],
        asset_pct_scale=scale,
        fee_rate=TRADING_FEE_RATE,
        ignore_weight_split=bool(trade.get("conviction_buy")),
    )


def _print_header(title: str) -> None:
    print(f"\n=== {title} ===")


def check_config() -> list[str]:
    """필수 설정 확인. 문제 목록 반환."""
    issues: list[str] = []
    _print_header("1. 설정")
    print(f"  SYMBOL={SYMBOL}")
    print(f"  GT_SIGNAL_CAUSAL={GT_SIGNAL_CAUSAL} (live_sizing={live_sizing_enabled()})")
    print(f"  LIVE_TRADING_ENABLED={LIVE_TRADING_ENABLED}")
    print(f"  SIM_PRIMARY_SIZING={SIM_PRIMARY_SIZING}")
    dd = load_hybrid_dd_params()
    print(f"  hybrid DD: large={dd.get('dd_large_pct')}% medium={dd.get('dd_medium_pct')}%")
    print(
        f"  LIVE 한도: daily_max={LIVE_DAILY_KRW_MAX:,} "
        f"max_trades={LIVE_MAX_TRADES_PER_DAY} "
        f"loss_limit={LIVE_DAILY_LOSS_LIMIT_KRW:,} "
        f"cooldown={LIVE_COOLDOWN_MIN}min"
    )
    print(
        f"  06 루프: sleep={MONITOR_LOOP_SLEEP_SEC}s · "
        f"live_eval_cache={MATCH_LIVE_CACHE_SEC}s · bar={MATCH_PRIMARY_INTERVAL}m"
    )
    print("  체결 엔진: sim_causal_hybrid (hybrid_sim_execution)")
    print(f"  GT_INITIAL_CASH_KRW=₩{GT_INITIAL_CASH_KRW:,}")
    rules = load_monitor_rules()
    print(f"  monitor_rules={[r['rule_id'] for r in rules]}")
    if not GT_SIGNAL_CAUSAL:
        issues.append("GT_SIGNAL_CAUSAL=0 — hybrid live sizing 비활성")
    if LIVE_TRADING_ENABLED:
        issues.append("LIVE_TRADING_ENABLED=1 — dry-run 점검 시 0 권장")
    if len(rules) != 2:
        issues.append(f"monitor_rules {len(rules)}개 (기대 2)")
    expected = {"buy_compound_tight", "sell_mtf_cross_all_tf"}
    got = {r["rule_id"] for r in rules}
    if got != expected:
        issues.append(f"monitor_rules 불일치: {got}")
    return issues


def check_capital_alignment() -> list[str]:
    """
    초기 자금 40만 원 기준 원화 한도·알림 비율 점검 (100만 시대 ×0.4).

    Returns:
        불일치 시 이슈 문자열 목록.
    """
    issues: list[str] = []
    _print_header("1b. 초기 자금·비율 (40만 원)")
    ic = int(GT_INITIAL_CASH_KRW)
    expected = {
        "GT_INITIAL_CASH_KRW": 400_000,
        "MONITOR_ALERT_KRW_AMOUNT": int(ic * 0.10),
        "LIVE_ORDER_KRW": int(ic * 0.10),
        "LIVE_DAILY_LOSS_LIMIT_KRW": int(ic * 0.05),
        "LIVE_DAILY_KRW_MAX": int(ic * 10),
    }
    actual = {
        "GT_INITIAL_CASH_KRW": ic,
        "MONITOR_ALERT_KRW_AMOUNT": int(MONITOR_ALERT_KRW_AMOUNT),
        "LIVE_ORDER_KRW": int(LIVE_ORDER_KRW),
        "LIVE_DAILY_LOSS_LIMIT_KRW": int(LIVE_DAILY_LOSS_LIMIT_KRW),
        "LIVE_DAILY_KRW_MAX": int(LIVE_DAILY_KRW_MAX),
    }
    for key, exp in expected.items():
        got = actual[key]
        ok = got == exp
        mark = "OK" if ok else "WARN"
        print(f"  [{mark}] {key}={got:,} (기대 {exp:,})")
        if not ok:
            issues.append(f"{key}={got:,} ≠ 기대 {exp:,}")
    paper = PaperPortfolio.load()
    if int(paper.cash_krw) != ic and paper.qty < 1e-12:
        issues.append(
            f"paper 현금 ₩{paper.cash_krw:,.0f} ≠ 초기 ₩{ic:,} (보유 없을 때)"
        )
    elif int(getattr(paper, "initial_cash_krw", 0) or paper.cash_krw) != ic:
        print(f"  [INFO] paper 운용 중 (cash=₩{paper.cash_krw:,.0f})")
    return issues


def check_tier_sizing(df) -> list[str]:
    """hybrid vs conviction tier 금액 비교 (enhanced=False가 primary)."""
    issues: list[str] = []
    _print_header("2. hybrid tier 사이징 (시나리오)")
    price = 487.0
    ic = int(GT_INITIAL_CASH_KRW)
    scenarios = [
        ("신규·소형DD(1%)", ic, 0.0, 1.0, {}),
        ("신규·대형DD(6%)", ic, 0.0, 6.0, {}),
        ("복리·과거large leg", ic * 5, 2000.0, 3.0, {"completed_leg_ret": {1: 25.0}}),
    ]
    for label, cash, qty, dd, extra in scenarios:
        hybrid_amt = _plan_with_dd(
            cash,
            qty,
            price,
            dd,
            enhanced=False,
            completed_leg_ret=extra.get("completed_leg_ret"),
        )
        conv_amt = _plan_with_dd(
            cash,
            qty,
            price,
            dd,
            enhanced=True,
            completed_leg_ret=extra.get("completed_leg_ret"),
        )
        print(
            f"  [{label}] cash={cash:,} hybrid={hybrid_amt:,} "
            f"conviction={conv_amt:,} (conviction 배포 금지)"
        )
        if hybrid_amt <= 0:
            issues.append(f"hybrid 금액 0: {label}")
    return issues


def check_paper_replay(df) -> list[str]:
    """paper signal_history → 시뮬 replay 잔고 일치."""
    issues: list[str] = []
    _print_header("3. paper 시뮬 replay")
    paper = PaperPortfolio.load()
    hist = paper.signal_history
    print(f"  signal_history={len(hist)} · saved cash=₩{paper.cash_krw:,.0f} qty={paper.qty:.4f}")
    if not hist:
        print("  (이력 없음 — 신규 dry-run)")
        return issues
    approved = load_ev_wf_approved_rule_ids()
    replayed, _ = replay_paper_portfolio(hist, df, approved_buy_rules=approved)
    cash_diff = abs(replayed.cash_krw - paper.cash_krw)
    qty_diff = abs(replayed.qty - paper.qty)
    print(
        f"  replay cash=₩{replayed.cash_krw:,.0f} qty={replayed.qty:.4f} "
        f"(Δcash={cash_diff:,.0f} Δqty={qty_diff:.6f})"
    )
    if cash_diff > 1.0 or qty_diff > 1e-6:
        issues.append(
            "paper_portfolio.json 과 sim replay 불일치 — "
            "signal_history 갱신 후 06 --once 1회 권장"
        )
    return issues


def check_live_limits() -> None:
    """시뮬 대비 실거래 일한도 영향 안내."""
    _print_header("4. 실거래 한도 vs hybrid tier")
    st = LivePositionState()
    mon = Monitor(cooldown_file=None)
    frames = load_frames_from_db(mon, SYMBOL, lookback_days=CHART_LOOKBACK_DAYS)
    df = frames.get(MATCH_PRIMARY_INTERVAL)
    price = 487.0
    if df is not None and not df.empty:
        close_s = _close_series_from_df(df)
        if not close_s.empty:
            price = float(close_s.iloc[-1])
    planned = st.plan_buy_amount_krw(
        str(df.index[-1]) if df is not None and not df.empty else "2026-06-01 12:00:00",
        price,
        float(GT_INITIAL_CASH_KRW),
        0.0,
        df,
        enhanced=False,
        fee_rate=TRADING_FEE_RATE,
    )
    trader = LiveTrader()
    ok, reason = trader._can_trade("buy_compound_tight", planned)
    print(f"  현재가={price:,.0f} · hybrid planned={planned:,}")
    print(f"  LIVE_DAILY_KRW_MAX={LIVE_DAILY_KRW_MAX:,} → _can_trade={ok} ({reason or 'OK'})")
    if planned > LIVE_DAILY_KRW_MAX:
        print(
            "  WARN: hybrid 1회 매수액이 일한도 초과 가능 — "
            "파일럿은 의도적 제한, 시뮬(+1121%)과 괴리 발생"
        )


def check_live_eval() -> None:
    """현재 시점 규칙 발화."""
    _print_header("5. 현재 발화 (live_eval)")
    fired = evaluate_live_rules(force_refresh=True)
    if not fired:
        print("  발화 없음 (정상 — 신호 대기)")
        return
    for hit in fired:
        print(f"  {hit['side']} {hit['rule_id']} @ {hit['dt']} close={hit['close']}")


def run_dryrun_once() -> None:
    """06 1회 dry-run."""
    _print_header("6. 06_execute_live --once (dry-run)")
    LiveTrader().run_once()


def write_verification_report(issues: list[str], out_path: Path) -> None:
    """Phase A 결과를 docs/05_ops에 기록."""
    out_path.parent.mkdir(parents=True, exist_ok=True)
    status = "PASS" if not issues else "WARN"
    lines = [
        "# Live Phase A — dry-run 검증",
        "",
        f"- 일시: {datetime.now():%Y-%m-%d %H:%M:%S}",
        f"- 결과: **{status}**",
        "",
        "## Plan (목적)",
        "",
        "- 06 dry-run/live 체결이 `hybrid_sim_execution`(sim_causal_hybrid)과 정합인지 확인",
        "- conviction tier(`enhanced=True`) 미사용 확인",
        "- 실거래 한도가 hybrid tier와 어떻게 상호작용하는지 기록",
        "",
        "## Do (실행)",
        "",
        "```bash",
        "python scripts/06_verify_live_dryrun.py",
        "python scripts/06_execute_live.py --once",
        "```",
        "",
        "## Check (점검 결과)",
        "",
        f"- GT_SIGNAL_CAUSAL={GT_SIGNAL_CAUSAL}",
        f"- LIVE_TRADING_ENABLED={LIVE_TRADING_ENABLED}",
        f"- monitor_rules: buy_compound_tight, sell_mtf_cross_all_tf",
        f"- hybrid DD: {load_hybrid_dd_params()}",
        "",
    ]
    if issues:
        lines.append("### 이슈")
        lines.append("")
        for i in issues:
            lines.append(f"- {i}")
        lines.append("")
    lines.extend(
        [
            "## Act (다음 단계)",
            "",
            "1. `05_run_monitor.py` 1~2일 병행 (알림만)",
            "2. `.env` 파일럿 한도 확정 후 `LIVE_TRADING_ENABLED=1`",
            "3. 1~2주 실계좌 PnL·슬리피지 기록 (본 문서 갱신)",
            "",
            "## Kill switch",
            "",
            "- `LIVE_TRADING_ENABLED=0` + 06 프로세스 중지",
            "- 빗썸 앱 수동 청산",
            "",
        ]
    )
    out_path.write_text("\n".join(lines), encoding="utf-8")
    print(f"\n[저장] {out_path}")


def main() -> int:
    """Phase A 검증 실행."""
    print("[06_verify] Phase A dry-run 검증 시작")
    issues = check_config()
    issues.extend(check_capital_alignment())
    mon = Monitor(cooldown_file=None)
    frames = load_frames_from_db(mon, SYMBOL, lookback_days=CHART_LOOKBACK_DAYS)
    df = frames.get(MATCH_PRIMARY_INTERVAL)
    if df is None or df.empty:
        issues.append("3m OHLC 없음 — 01_download 필요")
    else:
        issues.extend(check_tier_sizing(df))
        issues.extend(check_paper_replay(df))
    check_live_limits()
    check_live_eval()
    run_dryrun_once()
    out = (
        Path(__file__).resolve().parents[1]
        / "docs"
        / "05_ops"
        / f"live_verification_{datetime.now():%Y%m%d}.md"
    )
    write_verification_report(issues, out)
    _print_header("요약")
    if issues:
        for i in issues:
            print(f"  WARN: {i}")
        print("  → 이슈 확인 후 Phase B(소액 파일럿) 진행")
        return 1
    print("  Phase A PASS — Phase B(소액 LIVE_TRADING_ENABLED=1) 준비 완료")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
