"""임시 코드 — Opus 가 놓친 축만 좁혀서 4개 모델에 다시 묻는다.

왜 좁은 질문인가. 열린 판정("어느 후보가 나은가")은 모델이 안 본 축을 안 본
채로 점수만 매기고 넘어간다 — 기존 Gemini 가 총구 방향에 A6:B14 를 준 것이
그 결과다. 그래서 여기서는 **개수·접촉·가림 범위**만 묻는다. 답이 하나로
정해지는 질문이라 틀리면 틀린 것이 드러난다.

대조군을 함께 넣는다. 같은 질문을 답이 명백한 이미지에도 던져, 모델이
질문에 끌려가 "있다"고 답하는 것인지 실제로 보고 답하는 것인지 가른다.

모델 4종: claude-opus(Opus 5) · claude-fable(Fable 5) · gpt(GPT-5.6 Sol) ·
gemini-pro(Gemini 3.1 Pro). 질문마다 2회씩 물어 안정성도 본다.
"""
from __future__ import annotations

import json
import sys
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import Any, Dict, List

sys.path.insert(0, str(Path(__file__).resolve().parent))

from app.modules.llm.llm_client import call_structured  # noqa: E402
from app.modules.pipeline.multiroll_gemini import png_part  # noqa: E402

ROOT = Path(__file__).resolve().parent.parent
RECIPE = ROOT / ("projects/e716bafb-24bb-42b7-aea0-fdb383844ee8/images/"
                 "d6a9aa85-b75e-400c-980c-4ee7e876a15b/scene/recipe")

MODELS = ["claude-opus", "claude-fable", "gpt", "gemini-pro"]
ROLLS = 2

SYS = """\
You are looking at a single photograph and answering one narrow factual
question about what is physically visible in it.

Answer only from the pixels. Do not infer what the scene "should" contain, and
do not smooth over an oddity because a normal car or a normal room would not
have it — if the picture shows something impossible, say that it shows it.

Count partial and occluded instances too, and say where each one sits in the
frame. If you are unsure, say so in `confidence` rather than guessing a tidy
number.

Answer in Korean."""

SCHEMA = {
    "type": "object",
    "properties": {
        "answer": {"type": "string"},
        "count": {"type": "integer"},
        "where": {"type": "string"},
        "confidence": {"type": "string",
                       "enum": ["high", "medium", "low"]},
    },
    "required": ["answer", "count", "where", "confidence"],
    "additionalProperties": False,
}

# (질문 id, 대상 이미지, 질문, 기대되는 정답 — 사용자/내 육안, 대조군 여부)
PROBES = [
    ("핸들개수", "S13sh3_a",
     "이 사진 안에서 차량 조향 장치(스티어링 휠)의 림(둥근 테두리)이 몇 개 보이는가?"
     " 일부만 보이거나 다른 물체에 가려진 것도 센다. `count` 에 개수를 적고,"
     " `where` 에 각각이 화면 어느 위치에 있는지 적어라.",
     "사용자·나: 이중으로 겹쳐 보임 / Opus: 1개", False),
    ("핸들개수_대조", "S13sh3_b",
     "이 사진 안에서 차량 조향 장치(스티어링 휠)의 림(둥근 테두리)이 몇 개 보이는가?"
     " 일부만 보이거나 다른 물체에 가려진 것도 센다. `count` 에 개수를 적고,"
     " `where` 에 각각이 화면 어느 위치에 있는지 적어라.",
     "명백히 1개 (대조군)", True),
    ("폰지지", "S42sh4_b",
     "이 사진에서 휴대전화는 무엇에 닿아 지지되고 있는가?"
     " 사람의 손·거치대·평평한 표면 중 무엇이 닿아 있는지, 아니면 닿은 것이"
     " 아무것도 없는지 `answer` 에 적어라. `count` 에는 휴대전화에 닿아 있는"
     " 지지물의 개수를 적는다(없으면 0).",
     "사용자·나: 손 없이 떠 있음(0) / Opus: 유지 선택", False),
    ("폰지지_대조", "S42sh4_c",
     "이 사진에서 휴대전화는 무엇에 닿아 지지되고 있는가?"
     " 사람의 손·거치대·평평한 표면 중 무엇이 닿아 있는지, 아니면 닿은 것이"
     " 아무것도 없는지 `answer` 에 적어라. `count` 에는 휴대전화에 닿아 있는"
     " 지지물의 개수를 적는다(없으면 0).",
     "명백히 손이 쥐고 있음(1) (대조군)", True),
    ("미러얼굴", "S15sh5_c",
     "이 사진의 룸미러(실내 백미러) 안에 운전자의 얼굴이 비쳐 보이는가?"
     " 보인다면 얼굴 전체가 보이는지, 일부만 보이는지 `answer` 에 적고,"
     " 일부라면 어느 부분이 가려지거나 프레임 밖으로 잘렸는지 `where` 에 적어라."
     " `count` 에는 거울 안에 얼굴이 보이는 사람의 수를 적는다.",
     "사용자: 얼굴이 반 정도만 나옴", False),
]


# ── 열린 판정 — opus_pilot 과 **같은** 선정 프롬프트·스키마를 4모델에 ──────
# 좁은 질문만으로는 "물으면 보는가"만 알 수 있고, 실제 파이프라인이 쓰는
# 열린 판정에서 같은 축을 스스로 짚는지는 알 수 없다. 둘 다 돌려 가른다.
OPEN_STEMS = ["S13sh3", "S42sh4", "S15sh5"]


def ask_open(model: str, stem: str, records, size_idx, roll: int):
    from opus_pilot import SELECT_SYS, resolve_refs, select_schema
    from app.modules.pipeline.multiroll_gemini import ref_parts
    from app.modules.pipeline.multiroll_select import _compose_critique_prompt

    rec = records[stem]
    labels = sorted(p.stem.split("_")[-1].upper()
                    for p in RECIPE.glob(f"{stem}_[abc].png"))
    roll_prompts = rec.get("roll_prompts") or {}
    parts: List[Dict[str, Any]] = [{
        "type": "text",
        "text": ("THE BRIEF (every candidate was made from this):\n"
                 + _compose_critique_prompt(rec.get("prompt", ""),
                                            roll_prompts, labels[0],
                                            shared_prompt=None))}]
    refs, _ = resolve_refs(
        (rec.get("roll_refs") or {}).get(labels[0]) or rec.get("refs"),
        size_idx)
    parts += ref_parts(refs)
    for lab in labels:
        parts.append({"type": "text", "text": f"Candidate {lab}:"})
        parts.append(png_part(RECIPE / f"{stem}_{lab.lower()}.png"))
    tag = f"open_{stem}_{model.replace('-', '')}_{roll}"
    return call_structured(
        tag, SELECT_SYS, parts, select_schema(labels),
        project_config={tag: {"model": model}},
        schema_name="open_select", enable_fallback=False,
    )


def ask(model: str, probe, roll: int) -> Dict[str, Any]:
    pid, stem, question, _, _ = probe
    img = RECIPE / f"{stem}.png"
    parts: List[Dict[str, Any]] = [
        {"type": "text", "text": question}, png_part(img)]
    tag = f"probe_{pid}_{model.replace('-', '')}_{roll}"
    return call_structured(
        tag, SYS, parts, SCHEMA,
        project_config={tag: {"model": model}},
        schema_name="probe", enable_fallback=False,
    )


def main() -> None:
    out = Path(sys.argv[1] if len(sys.argv) > 1 else "probe_missed.json")
    jobs = [(m, p, r) for p in PROBES for m in MODELS
            for r in range(1, ROLLS + 1)]
    res: Dict[str, Dict[str, List[Any]]] = defaultdict(lambda: defaultdict(list))
    print(f"질문 {len(PROBES)} × 모델 {len(MODELS)} × {ROLLS}회 = {len(jobs)}콜",
          flush=True)

    with ThreadPoolExecutor(max_workers=6) as ex:
        futs = {ex.submit(ask, m, p, r): (m, p, r) for m, p, r in jobs}
        for f in as_completed(futs):
            m, p, r = futs[f]
            try:
                res[p[0]][m].append(f.result())
            except Exception as exc:  # noqa: BLE001
                res[p[0]][m].append({"error": f"{type(exc).__name__}: "
                                              f"{str(exc)[:160]}"})

    # ── 열린 판정 ──
    from opus_pilot import build_size_index
    records = json.loads((RECIPE / "records.json").read_text("utf-8"))
    size_idx = build_size_index()
    opens: Dict[str, Dict[str, List[Any]]] = defaultdict(
        lambda: defaultdict(list))
    ojobs = [(m, s, r) for s in OPEN_STEMS for m in MODELS
             for r in range(1, ROLLS + 1)]
    print(f"열린 판정 {len(ojobs)}콜", flush=True)
    with ThreadPoolExecutor(max_workers=6) as ex:
        futs = {ex.submit(ask_open, m, s, records, size_idx, r): (m, s)
                for m, s, r in ojobs}
        for f in as_completed(futs):
            m, s = futs[f]
            try:
                opens[s][m].append(f.result())
            except Exception as exc:  # noqa: BLE001
                opens[s][m].append({"error": f"{type(exc).__name__}: "
                                             f"{str(exc)[:160]}"})

    out.write_text(json.dumps({"probes": res, "open": opens},
                              ensure_ascii=False, indent=1), "utf-8")

    label = {"claude-opus": "Opus 5", "claude-fable": "Fable 5",
             "gpt": "GPT-5.6 Sol", "gemini-pro": "Gemini 3.1 Pro"}
    for pid, stem, _, expect, is_ctrl in PROBES:
        print(f"\n{'='*78}")
        print(f"[{pid}] {stem}{'  (대조군)' if is_ctrl else ''}")
        print(f"  기준: {expect}")
        for m in MODELS:
            for i, a in enumerate(res[pid][m], 1):
                if "error" in a:
                    print(f"  {label[m]:15s} #{i}  오류 {a['error'][:70]}")
                    continue
                print(f"  {label[m]:15s} #{i}  count={a['count']} "
                      f"[{a['confidence']}]  {a['answer'][:88]}")
                print(f"  {'':15s}     위치: {a['where'][:88]}")

    prior = {s: (records[s] or {}).get("selected") for s in OPEN_STEMS}
    truth = {"S13sh3": "세 후보 다 결함(사용자·나)",
             "S42sh4": "_c 가 최선(손이 폰을 쥠) — 단 브리프에 NO PEOPLE 조항",
             "S15sh5": "_a 가 자연스러움(나) / 브리프 충족은 _c (Opus)"}
    for stem in OPEN_STEMS:
        print(f"\n{'='*78}")
        print(f"[열린 판정] {stem}   기존 Gemini 선정={prior[stem]}")
        print(f"  기준: {truth[stem]}")
        for m in MODELS:
            for i, a in enumerate(opens[stem][m], 1):
                if "error" in a:
                    print(f"  {label[m]:15s} #{i}  오류 {a['error'][:70]}")
                    continue
                sc = "  ".join(f"{r['label']}{r['score']}"
                               for r in a.get("readings", []))
                fail = " ☠전부실패" if a.get("all_candidates_fail") else ""
                print(f"  {label[m]:15s} #{i}  선정={a['winner']}  [{sc}]{fail}")
                print(f"  {'':15s}     {str(a.get('why'))[:92]}")
    print(f"\n원본: {out}")


if __name__ == "__main__":
    main()
