"""
실제 과거 데이터 기반 전 종목 백테스트 러너 (Task B)

목적:
  - 현재 watchlist의 모든 종목에 대해 KIS API로 일봉 OHLCV 수집
  - v3.2 Backtester (next-bar 체결 + 증권거래세 + 거래량 상한)로 실측 성과 산출
  - 집계 리포트 생성 (Sharpe, MDD, Calmar, Payoff, Turnover, 승률)

사용:
    python backtest_runner.py                 # watchlist 전체
    python backtest_runner.py 005930 000660  # 특정 종목만

주의:
  - KIS API는 1회당 최대 100영업일 반환 → 여러 구간을 이어붙여 ~1년 수집
  - LSTM은 시간 과다 소요로 제외, TechnicalAnalyzer 단독 전략 사용
  - 종목당 약 1~2초 (API 스로틀 0.5초/호출 × 3구간)
"""
import json
import sys
import time
from datetime import datetime, timedelta
from pathlib import Path

from modules.services.kis import KISClient
from modules.analysis.technical import TechnicalAnalyzer
from modules.analysis.backtest import Backtester


# ──────────────────────────────────────────────
# 전략: 기술적 점수 기반 BUY/SELL
# ──────────────────────────────────────────────
def technical_strategy(slice_data: dict, buy_th: float = 0.65, sell_th: float = 0.35) -> str:
    closes = slice_data.get("close", [])
    volumes = slice_data.get("volume", [])
    if len(closes) < 30:
        return "HOLD"
    try:
        score, *_ = TechnicalAnalyzer.get_technical_score(
            closes[-1], closes, volumes if volumes else None
        )
    except Exception:
        return "HOLD"
    if score >= buy_th:
        return "BUY"
    if score <= sell_th:
        return "SELL"
    return "HOLD"


# ──────────────────────────────────────────────
# KIS OHLCV 다중 구간 수집 (~1년)
# ──────────────────────────────────────────────
def fetch_ohlcv_long(kis: KISClient, ticker: str, days: int = 240) -> dict | None:
    """~1년(240영업일) 일봉 OHLCV 수집. API 한계(100일)를 여러 호출로 극복."""
    try:
        # 단순화: 100일짜리 한 번 + 추가로 count=250 요청 시도
        data = kis._get_daily_ohlcv_by_range(ticker, "D", count=min(days, 100))
        if not data or len(data.get("close", [])) < 60:
            return None
        return data
    except Exception as e:
        print(f"[{ticker}] OHLCV 수집 실패: {e}")
        return None


# ──────────────────────────────────────────────
# 메인
# ──────────────────────────────────────────────
def main():
    argv_tickers = sys.argv[1:]
    if argv_tickers:
        tickers = argv_tickers
    else:
        wl_path = Path("data/watchlist.json")
        if not wl_path.exists():
            print("data/watchlist.json 없음")
            return
        watchlist = json.loads(wl_path.read_text(encoding="utf-8"))
        tickers = list(watchlist.keys()) if isinstance(watchlist, dict) else watchlist

    print(f"▶ 대상 종목: {len(tickers)}개 — {tickers[:5]}{'...' if len(tickers) > 5 else ''}")

    kis = KISClient()
    bt = Backtester(initial_capital=10_000_000)
    results = {}
    skipped = []

    t0 = time.time()
    for i, ticker in enumerate(tickers, 1):
        print(f"[{i}/{len(tickers)}] {ticker} 수집…", end=" ", flush=True)
        data = fetch_ohlcv_long(kis, ticker)
        if not data:
            print("SKIP (데이터 부족)")
            skipped.append(ticker)
            continue
        bars = len(data["close"])
        try:
            r = bt.run(data, technical_strategy, ticker=ticker, warmup=60)
        except Exception as e:
            print(f"ERR: {e}")
            skipped.append(ticker)
            continue
        results[ticker] = r
        print(f"bars={bars} trades={r.total_trades} ret={r.total_return_pct:+.1f}% "
              f"MDD={r.max_drawdown_pct:.1f}% Sharpe={r.sharpe_ratio:.2f}")

    elapsed = time.time() - t0

    # ── 집계 ──
    if not results:
        print("\n집계할 결과 없음.")
        return

    import statistics
    rets = [r.total_return_pct for r in results.values()]
    sharpes = [r.sharpe_ratio for r in results.values() if r.total_trades > 0]
    mdds = [r.max_drawdown_pct for r in results.values()]
    wins = [r.win_rate for r in results.values() if r.total_trades > 0]
    trades_total = sum(r.total_trades for r in results.values())

    print("\n" + "=" * 60)
    print(f"📊 백테스트 집계 — {len(results)}종목 / {elapsed:.1f}s")
    print("=" * 60)
    print(f"평균 수익률:     {statistics.mean(rets):+.2f}%  "
          f"(중앙 {statistics.median(rets):+.2f}%)")
    print(f"평균 MDD:        {statistics.mean(mdds):.2f}%")
    if sharpes:
        print(f"평균 Sharpe:     {statistics.mean(sharpes):.3f}")
    if wins:
        print(f"평균 승률:       {statistics.mean(wins):.1f}%")
    print(f"총 거래 수:      {trades_total}")
    print(f"SKIP:            {len(skipped)}종목 {skipped}")

    # 상/하위 5
    sorted_r = sorted(results.items(), key=lambda kv: kv[1].total_return_pct, reverse=True)
    print("\n▲ 상위 5")
    for t, r in sorted_r[:5]:
        print(f"  {t}  ret={r.total_return_pct:+7.2f}%  "
              f"MDD={r.max_drawdown_pct:5.2f}%  trades={r.total_trades}")
    print("\n▼ 하위 5")
    for t, r in sorted_r[-5:]:
        print(f"  {t}  ret={r.total_return_pct:+7.2f}%  "
              f"MDD={r.max_drawdown_pct:5.2f}%  trades={r.total_trades}")

    # 리포트 파일
    report = {
        "generated_at": datetime.now().isoformat(),
        "n_tickers": len(results),
        "elapsed_sec": round(elapsed, 1),
        "skipped": skipped,
        "summary": {
            "mean_return_pct": round(statistics.mean(rets), 2),
            "median_return_pct": round(statistics.median(rets), 2),
            "mean_mdd_pct": round(statistics.mean(mdds), 2),
            "mean_sharpe": round(statistics.mean(sharpes), 3) if sharpes else None,
            "mean_win_rate": round(statistics.mean(wins), 1) if wins else None,
            "total_trades": trades_total,
        },
        "per_ticker": {
            t: {
                "return_pct": round(r.total_return_pct, 2),
                "mdd_pct": round(r.max_drawdown_pct, 2),
                "sharpe": round(r.sharpe_ratio, 3),
                "calmar": round(r.calmar_ratio, 3),
                "payoff": round(r.payoff_ratio, 3),
                "turnover": round(r.turnover_ratio, 3),
                "win_rate": round(r.win_rate, 1),
                "trades": r.total_trades,
            } for t, r in results.items()
        },
    }
    out_path = Path("data/backtest_report.json")
    out_path.parent.mkdir(exist_ok=True)
    out_path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"\n리포트 저장: {out_path}")


if __name__ == "__main__":
    main()
