"""
인과 GT leg 엔진 파라미터 그리드 탐색·최적 저장.
"""

from __future__ import annotations

import json
from itertools import product
from pathlib import Path
from typing import Any

from config import (
    CHART_LOOKBACK_DAYS,
    GT_BUY_BB_MAX,
    GT_BUY_MIN_SWING_PCT,
    GT_MIN_SWING_PCT,
    GT_PIVOT_ORDER,
    MATCH_PRIMARY_INTERVAL,
    SYMBOL,
)
from deepcoin.data.mtf_bb import load_frames_from_db
from deepcoin.ground_truth.causal_gt_trades import simulate_causal_gt_portfolio
from deepcoin.ground_truth.ground_truth import load_ground_truth
from deepcoin.ops.monitor import Monitor
from deepcoin.paths import MATCHING_CAUSAL_GT_CALIBRATION_JSON, resolve_ground_truth_file


def default_causal_gt_params() -> dict[str, Any]:
    """
    인과 GT leg 엔진 기본 파라미터.

    Returns:
        build_causal_split_buy_peak_sell_trades 키워드 인자.
    """
    from config import (
        CAUSAL_GT_MIN_BARS_BETWEEN_LEGS,
        CAUSAL_GT_MIN_LEG_PCT,
        CAUSAL_GT_PEAK_MODE,
        CAUSAL_GT_USE_LOCAL_TROUGH,
    )

    return {
        "pivot_order": GT_PIVOT_ORDER,
        "buy_swing_pct": GT_BUY_MIN_SWING_PCT,
        "sell_swing_pct": GT_MIN_SWING_PCT,
        "bb_max": GT_BUY_BB_MAX,
        "min_leg_pct": CAUSAL_GT_MIN_LEG_PCT,
        "use_local_trough": CAUSAL_GT_USE_LOCAL_TROUGH,
        "peak_mode": CAUSAL_GT_PEAK_MODE,
        "min_bars_between_legs": CAUSAL_GT_MIN_BARS_BETWEEN_LEGS,
    }


def load_causal_gt_params(path: Path | None = None) -> dict[str, Any]:
    """
    캘리브레이션 JSON 또는 config 기본값.

    Args:
        path: JSON 경로. None이면 MATCHING_CAUSAL_GT_CALIBRATION_JSON.

    Returns:
        best params dict.
    """
    p = path or MATCHING_CAUSAL_GT_CALIBRATION_JSON
    if p.is_file():
        data = json.loads(p.read_text(encoding="utf-8"))
        best = data.get("best_params") or data.get("params")
        if best:
            return dict(best)
    return default_causal_gt_params()


def _grid_space() -> dict[str, list[Any]]:
    """탐색 그리드 (로컬 peak 최적화 반영, 조합 ~864)."""
    return {
        "peak_mode": ["local", "zigzag"],
        "pivot_order": [8, 10, 12, 15],
        "buy_swing_pct": [2.0, 2.5, 3.0],
        "sell_swing_pct": [3.0, 4.0],
        "bb_max": [0.55, 0.65, 0.75],
        "min_leg_pct": [3.0, 5.0, 8.0],
        "min_bars_between_legs": [60, 90],
        "use_local_trough": [True, False],
    }


def run_causal_gt_calibration(
    *,
    min_trades: int = 30,
    top_n: int = 20,
    out_path: Path | None = None,
) -> dict[str, Any]:
    """
    그리드 탐색 후 최적 파라미터 JSON 저장.

    Args:
        min_trades: 최소 체결 수 미만 조합 제외.
        top_n: 상위 N개 기록.
        out_path: 저장 경로.

    Returns:
        calibration report dict.
    """
    gt = load_ground_truth(resolve_ground_truth_file()) or {}
    mark = float((gt.get("summary") or {}).get("mark_price") or 0)
    gt_pnl = float(
        (gt.get("summary") or {}).get("pnl_pct")
        or 0
    )

    mon = Monitor(cooldown_file=None)
    frames = load_frames_from_db(mon, SYMBOL, lookback_days=CHART_LOOKBACK_DAYS)
    df = frames[MATCH_PRIMARY_INTERVAL].copy()

    grid = _grid_space()
    keys = list(grid.keys())
    results: list[dict[str, Any]] = []
    total = 1
    for k in keys:
        total *= len(grid[k])

    print(f"[causal_gt] 그리드 {total} 조합 탐색...")
    done = 0
    for combo in product(*(grid[k] for k in keys)):
        params = dict(zip(keys, combo))
        r = simulate_causal_gt_portfolio(df, last_price=mark or None, **params)
        tc = int(r.get("trade_count") or 0)
        done += 1
        if done % 200 == 0:
            print(f"  ... {done}/{total}")
        if tc < min_trades:
            continue
        pnl = float(r.get("pnl_pct") or 0)
        results.append(
            {
                "pnl_pct": round(pnl, 2),
                "trade_count": tc,
                "leg_count": r.get("leg_count", 0),
                "max_drawdown_pct": r.get("max_drawdown_pct"),
                "capture_ratio": round(pnl / gt_pnl, 4) if gt_pnl else 0,
                "params": params,
            }
        )

    results.sort(key=lambda x: x["pnl_pct"], reverse=True)
    best = results[0] if results else None
    report: dict[str, Any] = {
        "symbol": SYMBOL,
        "interval_min": MATCH_PRIMARY_INTERVAL,
        "gt_pnl_pct": gt_pnl,
        "grid_combinations": total,
        "valid_combinations": len(results),
        "min_trades": min_trades,
        "best": best,
        "best_params": best["params"] if best else default_causal_gt_params(),
        "top": results[:top_n],
        "target_pnl_pct": 300.0,
        "target_met": bool(best and best["pnl_pct"] >= 300.0),
    }

    out = out_path or MATCHING_CAUSAL_GT_CALIBRATION_JSON
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"[causal_gt] 저장: {out}")
    if best:
        print(
            f"[causal_gt] 최적 PnL={best['pnl_pct']}% "
            f"trades={best['trade_count']} legs={best['leg_count']} "
            f"capture={best.get('capture_ratio', 0):.2%}"
        )
    else:
        print("[causal_gt] 유효 조합 없음")
    return report
