#!/usr/bin/env python3
"""
ev_new 差分の切り分け: 1レースについて stage1・predictions・オッズ・ev_new を一覧出力。

163 / 133 どちらでも同じコマンドを実行し、出力を diff する。

例:
  python3 scripts/diagnose_ev_new_one_race.py --race-date 2026-01-04 --track-code 06 --race-number 1
  python3 scripts/diagnose_ev_new_one_race.py --race-date 2026-01-04 --track-code 06 --race-number 1 --compare-maspis
  python3 scripts/diagnose_ev_new_one_race.py --race-date 2026-01-04 --track-code 06 --race-number 1 --out /tmp/diag_163.txt
"""
from __future__ import annotations

import argparse
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from ai.prediction_profiles import get_profiles
from ai.stage1_screening import run_stage1
from ai.win_prob_ev_new import attach_win_prob_and_ev, marks_by_ev_new
from collectors.db_util import connect

DEFAULT_MODEL = "ai/models/stage1_lgbm_enhanced.txt"
DEFAULT_META = "ai/models/stage1_lgbm_enhanced.meta.json"
MAS_API = "https://ai-horse-race.maspis.com/api/all_race_compare_profiles.php"


def _resolve_race_id(cur, race_date: str, track_code: str, race_number: int) -> int | None:
    cur.execute(
        """
        SELECT r.id
        FROM races r
        JOIN tracks t ON t.id = r.track_id
        WHERE r.race_date = %s AND t.code = %s AND r.race_number = %s
        LIMIT 1
        """,
        (race_date, track_code, race_number),
    )
    row = cur.fetchone()
    return int(row["id"]) if row else None


def _tansho_odds(cur, race_id: int) -> dict[int, float]:
    cur.execute(
        """
        SELECT combination, min_odds, max_odds
        FROM odds_lines
        WHERE race_id = %s AND odds_type = 'tansho'
        """,
        (race_id,),
    )
    out: dict[int, float] = {}
    for row in cur.fetchall():
        comb = str(row.get("combination") or "").strip()
        if not comb.isdigit():
            continue
        mn, mx = row.get("min_odds"), row.get("max_odds")
        out[int(comb)] = float(mx if mx is not None else mn)
    return out


def _ev_honmei(horses: list[dict]) -> tuple[int | None, float | None]:
    attach_win_prob_and_ev(horses)
    with_ev = [h for h in horses if h.get("ev_new") is not None]
    if not with_ev:
        return None, None
    marks = marks_by_ev_new(with_ev)
    if not marks:
        return None, None
    return marks[0].get("horse_number"), float(marks[0].get("ev_new") or 0)


def _fetch_maspis_ev(race_date: str, track_code: str, race_number: int) -> dict | None:
    params = urllib.parse.urlencode(
        {
            "profile_key": "ev_new",
            "from_date": race_date,
            "to_date": race_date,
            "limit": "50",
        }
    )
    url = f"{MAS_API}?{params}"
    try:
        req = urllib.request.Request(url, headers={"User-Agent": "diagnose_ev_new/1.0"})
        with urllib.request.urlopen(req, timeout=60) as resp:
            data = json.loads(resp.read().decode("utf-8"))
    except (urllib.error.URLError, json.JSONDecodeError) as e:
        return {"error": str(e)}
    for row in data.get("rows") or []:
        if (
            str(row.get("race_date")) == race_date
            and str(row.get("track_code")) == track_code
            and int(row.get("race_number") or 0) == race_number
        ):
            return row
    return None


def _file_fingerprint(path: str) -> str:
    if not os.path.isfile(path):
        return "MISSING"
    import hashlib

    h = hashlib.md5()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(1 << 20), b""):
            h.update(chunk)
    return h.hexdigest()


def main() -> None:
    ap = argparse.ArgumentParser(description="1レース ev_new パイプライン診断")
    ap.add_argument("--race-date", required=True, help="YYYY-MM-DD")
    ap.add_argument("--track-code", required=True, help="例: 06")
    ap.add_argument("--race-number", type=int, required=True)
    ap.add_argument("--model-path", default=DEFAULT_MODEL)
    ap.add_argument("--model-meta-path", default=DEFAULT_META)
    ap.add_argument("--compare-maspis", action="store_true", help="maspis ev_new と比較（163 からのみ有効）")
    ap.add_argument("--out", default=None, help="出力ファイル（未指定なら stdout）")
    args = ap.parse_args()

    lines: list[str] = []

    def out(s: str = "") -> None:
        lines.append(s)

    import socket

    out("=== ev_new 1レース診断 ===")
    out(f"host={socket.gethostname()}")
    out(f"race={args.race_date} track={args.track_code} R={args.race_number}")
    out()

    out("--- AI ファイル fingerprint (md5) ---")
    for rel in [
        "ai/stage1_screening.py",
        "ai/stage2_factors.py",
        "ai/stage1_5_adjustments.py",
        "ai/prediction_profiles.py",
        "ai/win_prob_ev_new.py",
        "scripts/import_all_race_ev_new_compare.py",
        "ai/models/stage1_lgbm_enhanced.txt",
        "ai/models/stage1_lgbm_enhanced.meta.json",
    ]:
        out(f"{rel}\t{_file_fingerprint(rel)}")
    out()

    profile = get_profiles("p6_latest")[0]
    out("--- p6_latest 設定 ---")
    for k in (
        "lookback_runs",
        "pass_rate",
        "enable_stage1_5",
        "enable_stage2",
        "enable_stage3",
        "stage3_min_prob",
        "stage3_odds_cap",
    ):
        out(f"{k}={getattr(profile, k, None)}")
    out()

    conn = connect()
    try:
        with conn.cursor() as cur:
            race_id = _resolve_race_id(cur, args.race_date, args.track_code, args.race_number)
            if race_id is None:
                out("ERROR: レースが DB にありません")
                text = "\n".join(lines) + "\n"
                if args.out:
                    with open(args.out, "w", encoding="utf-8") as f:
                        f.write(text)
                else:
                    sys.stdout.write(text)
                sys.exit(1)
            odds = _tansho_odds(cur, race_id)
            cur.execute(
                """
                SELECT combination, win_probability, expected_odds, rank_in_pred, note
                FROM predictions
                WHERE race_id = %s
                  AND prediction_type = 'stage1_screening_accuracy'
                  AND bet_type = 'horse'
                ORDER BY rank_in_pred ASC
                """,
                (race_id,),
            )
            pred_rows = list(cur.fetchall())
            cur.execute(
                """
                SELECT ai_honmei_umaban, ai_honmei_score, ai_honmei_name
                FROM all_race_ai_profile_compare
                WHERE profile_key = 'ev_new' AND race_id = %s
                """,
                (race_id,),
            )
            ev_row = cur.fetchone()
    finally:
        conn.close()

    out(f"race_id={race_id}")
    out(f"predictions_rows={len(pred_rows)}")
    out(f"odds_horses={len(odds)}")
    if ev_row:
        out(
            f"DB ev_new ◎={ev_row.get('ai_honmei_umaban')} "
            f"{ev_row.get('ai_honmei_name')} ev={ev_row.get('ai_honmei_score')}"
        )
    out()

    payload = run_stage1(
        race_id=race_id,
        lookback_runs=profile.lookback_runs,
        pass_rate=profile.pass_rate,
        enable_stage1_5=profile.enable_stage1_5,
        enable_stage2=profile.enable_stage2,
        enable_stage3=profile.enable_stage3,
        stage3_min_prob=profile.stage3_min_prob,
        stage3_odds_cap=profile.stage3_odds_cap,
        model_path=args.model_path,
        model_meta_path=args.model_meta_path,
    )

    out("--- fresh stage1（score_accuracy 降順・上位16頭）---")
    out("rank\thorse_no\tname\tscore_accuracy\tmodel_win_prob\todds")
    stage1_rows = sorted(
        payload.get("horses") or [],
        key=lambda x: float(x.get("score_accuracy") or x.get("score") or 0),
        reverse=True,
    )
    for i, r in enumerate(stage1_rows[:16], 1):
        hn = r.get("horse_number")
        od = odds.get(int(hn)) if hn is not None else None
        out(
            f"{i}\t{hn}\t{(r.get('horse_name') or '')[:16]}\t"
            f"{r.get('score_accuracy')}\t{r.get('model_win_prob')}\t{od}"
        )
    out()

    def horses_from_score_fn(label: str, score_fn) -> None:
        horses: list[dict] = []
        for row in pred_rows:
            comb = str(row.get("combination") or "").strip()
            if not comb.isdigit():
                continue
            hn = int(comb)
            if hn not in odds:
                continue
            note = {}
            raw = row.get("note")
            if raw:
                try:
                    note = json.loads(raw) if isinstance(raw, str) else dict(raw)
                except json.JSONDecodeError:
                    note = {}
            sc = score_fn(row, note)
            if sc is None:
                continue
            horses.append({"horse_number": hn, "model_score": float(sc), "odds": odds[hn]})
        hon, ev = _ev_honmei(horses)
        out(f"ev_new ◎ ({label}): horse={hon} ev={ev}")

    out("--- ev_new ◎（スコアの取り方別）---")
    horses_from_score_fn("DB win_probability", lambda r, n: r.get("win_probability"))
    horses_from_score_fn("DB note.score_accuracy", lambda r, n: n.get("score_accuracy"))
    horses_from_score_fn("DB note.score", lambda r, n: n.get("score"))

    fresh: list[dict] = []
    for r in payload.get("horses") or []:
        hn = r.get("horse_number")
        if hn is None or int(hn) not in odds:
            continue
        sc = r.get("score_accuracy") or r.get("score")
        if sc is None:
            continue
        fresh.append({"horse_number": int(hn), "model_score": float(sc), "odds": odds[int(hn)]})
    hon, ev = _ev_honmei(fresh)
    out(f"ev_new ◎ (fresh stage1 score_accuracy): horse={hon} ev={ev}")
    out()

    out("--- 期待値 上位8（fresh stage1 + score_accuracy）---")
    attach_win_prob_and_ev(fresh)
    for h in sorted(fresh, key=lambda x: float(x.get("ev_new") or 0), reverse=True)[:8]:
        out(
            f"#{h['horse_number']}\tscore={h['model_score']}\todds={h['odds']}\t"
            f"win%={h.get('win_prob_new_pct')}\tev={h.get('ev_new')}"
        )
    out()

    if args.compare_maspis:
        out("--- maspis ev_new（参照）---")
        m = _fetch_maspis_ev(args.race_date, args.track_code, args.race_number)
        if m is None:
            out("maspis: 行なし")
        elif m.get("error"):
            out(f"maspis: fetch error {m['error']}")
        else:
            out(
                f"maspis ◎={m.get('ai_honmei_umaban')} {m.get('ai_honmei_name')} "
                f"ev={m.get('ai_honmei_score')}"
            )

    text = "\n".join(lines) + "\n"
    if args.out:
        with open(args.out, "w", encoding="utf-8") as f:
            f.write(text)
        print(f"wrote {args.out}")
    else:
        sys.stdout.write(text)


if __name__ == "__main__":
    main()
