#!/usr/bin/env python3
"""
Phase C dry-run 종료 후 모의 수익률(참고) 집계.

- 입력: data/ops/paper_fires.jsonl (06 dry-run 발화 로그)
- 출력: docs/05_ops/phase_c_paper_report.json + 콘솔 요약

주의: 실계좌 수익이 아님. 발화가 N봉 후 가격으로 계산한 forward % 합산(참고).
      hybrid 복리 PnL은 simulation_report.html 과 다릅니다.
"""
from __future__ import annotations

import argparse
import json
import runpy
from datetime import datetime
from pathlib import Path

import numpy as np
import pandas as pd

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

from config import (  # noqa: E402
    MATCH_FORWARD_BARS,
    MATCH_PRIMARY_INTERVAL,
    SYMBOL,
    TRADING_FEE_RATE,
)
from deepcoin.matching.label_outcomes import _forward_ret_vectorized  # noqa: E402
from deepcoin.ops.monitor import Monitor  # noqa: E402
from deepcoin.ops.paper_portfolio import PaperPortfolio  # noqa: E402
from deepcoin.paths import (  # noqa: E402
    PAPER_FIRES_LOG,
    PAPER_WEEKLY_REPORT_JSON,
    PHASE_C_DAILY_DIR,
)

_FEE_PCT = TRADING_FEE_RATE * 2 * 100


def load_paper_fires(path: Path) -> pd.DataFrame:
    """paper_fires.jsonl → DataFrame."""
    if not path.is_file():
        return pd.DataFrame()
    rows = []
    for line in path.read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if line:
            rows.append(json.loads(line))
    if not rows:
        return pd.DataFrame()
    return pd.DataFrame(rows)


def attach_forward_returns(fires: pd.DataFrame, close_df: pd.DataFrame) -> pd.DataFrame:
    """
    would_trade=True 발화에 MATCH_FORWARD_BARS 기준 forward 수익률(%) 부여.

    Args:
        fires: paper 발화.
        close_df: 3분 종가 (datetime index).

    Returns:
        forward_ret_pct 컬럼 추가.
    """
    if fires.empty or close_df.empty:
        fires["forward_ret_pct"] = np.nan
        return fires

    close_df = close_df.sort_index()
    if not isinstance(close_df.index, pd.DatetimeIndex):
        close_df.index = pd.to_datetime(close_df.index)
    close_ts_ns = close_df.index.astype(np.int64).values
    close_px = close_df["Close"].astype(float).values

    sub = fires[fires["would_trade"] == True].copy()  # noqa: E712
    if sub.empty:
        fires["forward_ret_pct"] = np.nan
        return fires

    sig = pd.to_datetime(sub["signal_dt"])
    fire_ns = sig.astype(np.int64).values
    c0 = sub["close"].astype(float).values
    side = sub["side"].astype(str).values
    ret, valid = _forward_ret_vectorized(
        fire_ns, c0, close_ts_ns, close_px, side, MATCH_FORWARD_BARS, _FEE_PCT
    )
    fires = fires.copy()
    fires["forward_ret_pct"] = np.nan
    idx = sub.index
    fires.loc[idx, "forward_ret_pct"] = np.where(valid, ret, np.nan)
    return fires


def summarize(fires: pd.DataFrame, *, report_kind: str = "daily") -> dict:
    """
    집계 dict.

    Args:
        fires: forward_ret_pct 포함 발화 DataFrame.
        report_kind: daily | final.

    Returns:
        JSON 직렬화 가능 dict.
    """
    traded = fires[fires["would_trade"] == True]  # noqa: E712
    with_ret = traded[traded["forward_ret_pct"].notna()]
    out: dict = {
        "generated_at": datetime.now().isoformat(timespec="seconds"),
        "report_kind": report_kind,
        "symbol": SYMBOL,
        "forward_bars": MATCH_FORWARD_BARS,
        "fee_round_trip_pct": _FEE_PCT,
        "total_signals": int(len(fires)),
        "would_trade_count": int(len(traded)),
        "skipped_count": int(len(fires) - len(traded)),
        "labeled_count": int(len(with_ret)),
        "note": (
            "forward %는 발화별 참고 지표. "
            "총보유금액(equity)은 paper_portfolio 모의 체결 기준."
        ),
    }
    if not fires.empty and "ts" in fires.columns:
        out["log_from"] = str(fires["ts"].min())
        out["log_to"] = str(fires["ts"].max())
    buy_n = int((traded["side"] == "buy").sum()) if not traded.empty else 0
    sell_n = int((traded["side"] == "sell").sum()) if not traded.empty else 0
    out["buy_fires"] = buy_n
    out["sell_fires"] = sell_n
    if not with_ret.empty:
        out["mean_forward_ret_pct"] = round(float(with_ret["forward_ret_pct"].mean()), 4)
        out["sum_forward_ret_pct"] = round(float(with_ret["forward_ret_pct"].sum()), 4)
        by_side = (
            with_ret.groupby("side")["forward_ret_pct"]
            .agg(["count", "mean", "sum"])
            .round(4)
        )
        out["by_side"] = {k: v.to_dict() for k, v in by_side.iterrows()}
    by_rule = (
        traded.groupby("rule_id")
        .size()
        .to_dict()
        if not traded.empty
        else {}
    )
    out["fires_by_rule"] = by_rule
    return out


def build_phase_c_report(
    fires_path: Path | None = None,
    *,
    report_kind: str = "daily",
) -> tuple[dict, pd.DataFrame]:
    """
    paper_fires 로드 → forward % → 리포트 dict.

    Args:
        fires_path: jsonl 경로 (기본 PAPER_FIRES_LOG).
        report_kind: daily | final.

    Returns:
        (report, fires_with_returns) — 발화 없으면 ({}, empty DataFrame).
    """
    path = fires_path or PAPER_FIRES_LOG
    fires = load_paper_fires(path)
    if fires.empty:
        return {}, fires

    mon = Monitor(cooldown_file=None)
    df = mon.read_candles_from_db(SYMBOL, MATCH_PRIMARY_INTERVAL, max_rows=50000)
    if df.empty:
        df = mon.get_coin_some_data(SYMBOL, MATCH_PRIMARY_INTERVAL)
    if not isinstance(df.index, pd.DatetimeIndex):
        df = df.set_index(pd.to_datetime(df["datetime"]))

    fires = attach_forward_returns(fires, df)
    report = summarize(fires, report_kind=report_kind)

    mark = float(df["Close"].iloc[-1]) if not df.empty and "Close" in df.columns else 0.0
    paper = PaperPortfolio.load()
    report["paper_portfolio"] = paper.summary(mark)
    return report, fires


def format_report_text(report: dict) -> str:
    """사람이 읽기 쉬운 요약 텍스트."""
    kind = report.get("report_kind", "daily")
    title = "Phase C 최종 보고" if kind == "final" else "Phase C 중간 보고"
    lines = [
        f"=== {title} ({report.get('generated_at', '')}) ===",
        f"심볼: {report.get('symbol', '')}",
    ]
    pf = report.get("paper_portfolio") or {}
    if pf:
        lines.extend(
            [
                "--- 모의 계좌 (dry-run, 빗썸 잔고 미사용) ---",
                f"초기 자금: ₩{pf.get('initial_cash_krw', 0):,.0f}",
                f"현금: ₩{pf.get('cash_krw', 0):,.0f} · "
                f"보유 {pf.get('qty', 0):.4f} {report.get('symbol', '')} "
                f"(평가단가 ₩{pf.get('mark_price', 0):,.0f})",
                f"코인 평가: ₩{pf.get('coin_value_krw', 0):,.0f}",
                f"총보유금액: ₩{pf.get('equity_krw', 0):,.0f} "
                f"(손익 ₩{pf.get('pnl_krw', 0):+,.0f} / {pf.get('pnl_pct', 0):+.2f}%)",
            ]
        )
    lines.append(
        f"발화 합계: {report.get('total_signals', 0)} "
        f"(체결 {report.get('would_trade_count', 0)}, "
        f"매수 {report.get('buy_fires', 0)} / 매도 {report.get('sell_fires', 0)})"
    )
    if "log_from" in report:
        lines.append(f"로그 구간: {report['log_from']} ~ {report['log_to']}")
    if "sum_forward_ret_pct" in report:
        lines.append(
            f"모의 forward 합산: {report['sum_forward_ret_pct']}% "
            f"(평균 {report['mean_forward_ret_pct']}%, "
            f"{report.get('forward_bars')}봉 후, 참고용)"
        )
    else:
        lines.append("모의 forward: 라벨 가능 건 없음 (봉·발화 부족)")
    lines.append(report.get("note", ""))
    return "\n".join(lines)


def write_report_outputs(
    report: dict,
    *,
    json_path: Path | None = None,
    text_path: Path | None = None,
) -> None:
    """JSON·텍스트 리포트 저장."""
    if json_path:
        json_path.parent.mkdir(parents=True, exist_ok=True)
        json_path.write_text(
            json.dumps(report, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
    if text_path:
        text_path.parent.mkdir(parents=True, exist_ok=True)
        text_path.write_text(format_report_text(report), encoding="utf-8")


def append_verification_log(report: dict, verification_md: Path) -> None:
    """live_verification 일별 표 해당 날짜 행 갱신."""
    if not verification_md.is_file():
        return
    text = verification_md.read_text(encoding="utf-8")
    iso = report.get("generated_at", "")[:10]
    try:
        y, m, d = iso.split("-")
        day_label = f"{int(m)}/{int(d)}"
    except ValueError:
        return
    buy = report.get("buy_fires", 0)
    sell = report.get("sell_fires", 0)
    pf = report.get("paper_portfolio") or {}
    equity = pf.get("equity_krw", "-")
    pnl_pct = pf.get("pnl_pct", "-")
    kind = report.get("report_kind", "daily")
    memo = "C 최종" if kind == "final" else "중간보고"
    row = (
        f"| {day_label} | Y | - | Y | {buy} | {sell} | "
        f"총₩{equity} ({pnl_pct}%) {memo} |"
    )
    marker = "### 일별 기록"
    if marker not in text:
        return
    head, table = text.split(marker, 1)
    lines = table.splitlines()
    new_lines: list[str] = []
    replaced = False
    for line in lines:
        if line.startswith(f"| {day_label} |"):
            new_lines.append(row)
            replaced = True
        else:
            new_lines.append(line)
    if not replaced:
        new_lines.append(row)
    verification_md.write_text(head + marker + "\n".join(new_lines), encoding="utf-8")


def run_report(
    *,
    report_kind: str = "daily",
    stamp: str | None = None,
    update_verification: bool = True,
) -> dict:
    """
    리포트 생성·저장·콘솔 출력.

    Args:
        report_kind: daily | final.
        stamp: 파일명용 타임스탬프 (기본 now).
        update_verification: live_verification md 갱신 여부.

    Returns:
        report dict (빈 dict 가능).
    """
    report, fires = build_phase_c_report(report_kind=report_kind)
    if not report:
        print(f"[07] 발화 로그 없음: {PAPER_FIRES_LOG}")
        print("  Phase C 기간 06_execute_live.py (LIVE=0) 상시 실행 후 재시도")
        return {}

    stamp = stamp or datetime.now().strftime("%Y%m%d_%H%M")
    daily_dir = PHASE_C_DAILY_DIR
    write_report_outputs(
        report,
        json_path=daily_dir / f"report_{stamp}_{report_kind}.json",
        text_path=daily_dir / f"report_{stamp}_{report_kind}.txt",
    )
    write_report_outputs(report, json_path=PAPER_WEEKLY_REPORT_JSON)
    if update_verification:
        append_verification_log(
            report,
            Path(__file__).resolve().parents[1]
            / "docs/05_ops/live_verification_20260601.md",
        )

    print(format_report_text(report))
    print(f"[07] JSON: {PAPER_WEEKLY_REPORT_JSON}")
    print(f"[07] 일별: {daily_dir}/report_{stamp}_{report_kind}.*")
    return report


def main() -> None:
    """CLI: paper_fires → forward % → 리포트 저장."""
    parser = argparse.ArgumentParser(description="Phase C 모의 forward % 집계")
    parser.add_argument(
        "--kind",
        choices=("daily", "final"),
        default="daily",
        help="daily=중간, final=금요일 최종",
    )
    parser.add_argument("--no-verification-md", action="store_true")
    args = parser.parse_args()
    run_report(
        report_kind=args.kind,
        update_verification=not args.no_verification_md,
    )


if __name__ == "__main__":
    main()
