#!/usr/bin/env python3
"""PRUVIQ 검증 기록을 **제3자가** 재실행한다. 의존성 없음 — Python 3.8+ 표준 라이브러리만.

    curl -sO https://pruviq.com/data/verification/reverify.py
    python3 reverify.py https://pruviq.com/data/verification/records/<id>.json

왜 이 파일이 있나
-----------------
2026-09-19 에 외부 모델(gpt-6)이 우리 공개 주장을 스스로 재판정했고, 두 질문 모두
`could_i_reproduce: unknown` 이라 답했다. 사유가 정확했다 —

    "both point at scripts in our repo without publishing how to obtain them"

검증 기록의 `how_to_reverify.command` 가 우리 사설 저장소의 스크립트를 가리키고 있었다.
저장소는 비공개이고 API 는 공개다. 즉 **데이터는 줬는데 자를 안 줬다.**

「검증됨」은 타자가 같은 절차로 재판정할 수 있을 때만 쓸 수 있다. 그래서 자를 공개한다.
이 파일은 사설 모듈을 import 하지 않는다 — 누구나 받아서 그대로 돌릴 수 있다.

무엇을 하나
-----------
기록의 `inputs.phases` 를 **순서대로** 공개 API 에 재생한다. 각 단계에서

  · 요청  = 그 단계의 `request` + 기록의 `inputs.coins`(원래의 top_n 선택을 고정한다)
  · 대조① = 응답의 `provenance.engine_commit` · `data_snapshot_id` · `coins`
  · 대조② = 응답 값 vs 기록의 `result`

종료 코드 (기록의 `how_to_reverify.result_codes` 와 같다)
--------------------------------------------------------
  0  같은 엔진·같은 입력 스냅샷·같은 결과            → 재현됨
  1  같은 엔진·같은 입력인데 **결과가 다름**          → 재현 실패(우리 주장이 틀렸을 수 있다)
  2  엔진·데이터가 바뀌었거나 실행 불가              → **재현이 아니다**(통과도 실패도 아님)

rc 2 를 「통과」로 읽지 마라. 과거 데이터 파일은 이 계약이 보존하지 않으므로, 데이터가
바뀌면 재생이 불가능해지고 그건 우리 주장에 대한 판정이 아니다.

반박하는 법
-----------
인바운드 제출 엔드포인트는 **의도적으로 없다** — 이 사이트의 API 호스트는 실자금 워커를
같이 돌린다. 결과는 어디에 공개하든 좋고, 연락처는 https://pruviq.com/about/ 이다.
"""
import argparse
import json
import sys
import time
import urllib.error
import urllib.request

UA = "pruviq-reverify/1.0 (+https://pruviq.com/data/verification/reverify.py)"
DEFAULT_API = "https://api.pruviq.com"
COMPARED_PROVENANCE = ("engine_commit", "data_snapshot_id", "coins")
ALLOWED_ENDPOINTS = ("/simulate", "/simulate/validate")


def fetch_json(url, timeout=60):
    req = urllib.request.Request(url, headers={"User-Agent": UA})
    with urllib.request.urlopen(req, timeout=timeout) as r:
        return json.load(r)


def post_json(api_base, endpoint, payload, timeout=180):
    req = urllib.request.Request(
        api_base.rstrip("/") + endpoint,
        data=json.dumps(payload).encode("utf-8"),
        headers={"User-Agent": UA, "Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=timeout) as r:
        return json.load(r)


def replay(record, api_base, pace, verbose):
    """기록의 phases 를 재생한다. 반환값이 곧 종료 코드다."""
    phases = ((record.get("inputs") or {}).get("phases")) or []
    coins = (record.get("inputs") or {}).get("coins")
    if not phases:
        print("rc=2 — 기록에 inputs.phases 가 없다. 재현이 아니다.", file=sys.stderr)
        return 2
    if coins is None:
        print("rc=2 — 기록에 inputs.coins 가 없다(원래 선택을 고정할 수 없다).", file=sys.stderr)
        return 2

    mismatch = False
    for i, old in enumerate(phases, 1):
        endpoint = old.get("endpoint")
        if endpoint not in ALLOWED_ENDPOINTS:
            print(f"rc=2 — 단계 {i}: 지원하지 않는 endpoint {endpoint!r}", file=sys.stderr)
            return 2
        payload = dict(old.get("request") or {})
        payload["symbols"] = coins
        try:
            resp = post_json(api_base, endpoint, payload)
        except urllib.error.HTTPError as e:
            print(f"rc=2 — 단계 {i}: {endpoint} HTTP {e.code}. 실행 불가는 판정이 아니다.", file=sys.stderr)
            return 2
        except Exception as e:                                   # 네트워크·타임아웃
            print(f"rc=2 — 단계 {i}: {endpoint} 요청 실패({type(e).__name__}).", file=sys.stderr)
            return 2

        got = resp.get("provenance") or {}
        want = old.get("provenance") or {}
        drift = [k for k in COMPARED_PROVENANCE if got.get(k) != want.get(k)]
        if drift:
            print(f"rc=2 — 단계 {i}: 엔진/데이터가 바뀌었다 ({', '.join(drift)}).", file=sys.stderr)
            for k in drift:
                print(f"        {k}: 기록={want.get(k)!r}  지금={got.get(k)!r}", file=sys.stderr)
            return 2

        diffs = {k: (v, resp.get(k)) for k, v in (old.get("result") or {}).items()
                 if resp.get(k) != v}
        if diffs:
            mismatch = True
            print(f"단계 {i} ({endpoint}) — **결과 불일치** {len(diffs)}건")
            for k, (was, now) in list(diffs.items())[:12]:
                print(f"    {k}: 기록={was!r}  지금={now!r}")
        elif verbose:
            print(f"단계 {i} ({endpoint}) — 일치")
        if pace:
            time.sleep(pace)

    return 1 if mismatch else 0


def main():
    ap = argparse.ArgumentParser(description="PRUVIQ 검증 기록 독립 재실행")
    ap.add_argument("record_url", help="https://pruviq.com/data/verification/records/<id>.json")
    ap.add_argument("--api", default=DEFAULT_API, help=f"API 기준 주소 (기본 {DEFAULT_API})")
    ap.add_argument("--pace", type=float, default=1.0, help="단계 사이 대기 초 (기본 1.0)")
    ap.add_argument("-v", "--verbose", action="store_true")
    a = ap.parse_args()

    try:
        record = fetch_json(a.record_url)
    except Exception as e:
        print(f"rc=2 — 기록을 못 받았다: {type(e).__name__}", file=sys.stderr)
        return 2

    print(f"기록   {record.get('record_id', '?')[:16]}…  preset={record.get('preset_id')}")
    print(f"측정   {record.get('measured_at')}  기준={record.get('versions', {}).get('criteria_version')}")
    print(f"단계   {len(((record.get('inputs') or {}).get('phases')) or [])}개  코인={len((record.get('inputs') or {}).get('coins') or [])}종")
    print(f"API    {a.api}")
    print()

    rc = replay(record, a.api, a.pace, a.verbose)
    print()
    print({0: "rc=0 — 재현됨(같은 엔진·입력·결과).",
           1: "rc=1 — 같은 엔진·입력인데 결과가 다르다. 우리 주장이 틀렸을 수 있다.",
           2: "rc=2 — 재현이 아니다. 통과도 실패도 아니다."}[rc])
    return rc


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