"""A/B 도구가 **기존 표본을 조용히 덮지 않는가** (2026-08-29 Codex BLOCK).

두 도구 모두 고정 경로에 굽는다. `--run` 을 두 번 치면 옛 PNG 와
`runs.json` 이 확인 없이 갈렸다. 그 파일들은 눈가림 판정·HTML·probe 제외의
**근거**다 — 같은 experiment id 아래에서 표본만 바뀌면 앞서 적은 결론이
무엇을 보고 한 말인지 알 수 없게 된다. 돈이 아니라 **기록이 깨지는** 문제다.

★기본이 dry 인 것으로는 못 막는다. `--run` 재실행이 바로 그 자리다.

## 여기서 재는 것

1. 가드 자체 (`claim_output_dir`)
2. ★**도구가 provider 를 만들기 전에 그 가드를 지나는가** — 기존 파일이
   있으면 **gen 이 한 번도 안 불려야** 한다. 가드만 초록이고 도구가 그것을
   나중에 부르면 유료 호출이 이미 나간 뒤다.
"""
from __future__ import annotations

import importlib.util
import sys
from pathlib import Path

import pytest

TOOLS = Path(__file__).resolve().parents[2] / "tools" / "prompt_measure"


def _load(name: str):
    """도구를 모듈로 읽는다 — `_refs` 를 찾을 수 있게 경로를 먼저 넣는다."""
    if str(TOOLS) not in sys.path:
        sys.path.insert(0, str(TOOLS))
    spec = importlib.util.spec_from_file_location(f"_tool_{name}", TOOLS / f"{name}.py")
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)          # type: ignore[union-attr]
    return mod


# ── 1) 가드 자체 ──────────────────────────────────────────────────────

def _claim():
    if str(TOOLS) not in sys.path:
        sys.path.insert(0, str(TOOLS))
    from _refs import claim_output_dir

    return claim_output_dir


def test_빈_자리는_잡는다(tmp_path: Path):
    out = _claim()(tmp_path / "exp", ["old_1.png", "new_1.png"])
    assert out.is_dir()


def test_png_하나만_있어도_선다(tmp_path: Path):
    d = tmp_path / "exp"
    d.mkdir()
    (d / "old_1.png").write_bytes(b"x")
    with pytest.raises(SystemExit) as e:
        _claim()(d, ["old_1.png", "new_1.png"])
    assert "old_1.png" in str(e.value)


def test_runs_json_만_있어도_선다(tmp_path: Path):
    """판정 근거는 PNG 만이 아니다."""
    d = tmp_path / "exp"
    d.mkdir()
    (d / "runs.json").write_text("{}")
    with pytest.raises(SystemExit) as e:
        _claim()(d, ["old_1.png"])
    assert "runs.json" in str(e.value)


def test_새_run_id_는_통과한다(tmp_path: Path):
    d = tmp_path / "exp"
    d.mkdir()
    (d / "old_1.png").write_bytes(b"x")
    out = _claim()(d, ["old_1.png"], run_id="2회차")
    assert out == d / "2회차" and out.is_dir()


def test_이미_쓴_run_id_는_다시_안_준다(tmp_path: Path):
    d = tmp_path / "exp"
    (d / "2회차").mkdir(parents=True)
    (d / "2회차" / "old_1.png").write_bytes(b"x")
    with pytest.raises(SystemExit):
        _claim()(d, ["old_1.png"], run_id="2회차")


def test_지우라고_하지_않는다(tmp_path: Path):
    """도구가 사람의 판단을 대신하지 않는다 — 지우는 것은 사람이 한다."""
    d = tmp_path / "exp"
    d.mkdir()
    (d / "runs.json").write_text("{}")
    with pytest.raises(SystemExit) as e:
        _claim()(d, [])
    msg = str(e.value)
    assert "--run-id" in msg
    assert "사람이 직접 지운다" in msg


# ── 2) ★도구가 gen 을 만들기 전에 선다 ────────────────────────────────

@pytest.mark.parametrize("tool_name, out_names", [
    ("carried_name_ab", ["old_r1_a.png"]),
    ("carried_name_regen_ab", ["old_1.png"]),
])
def test_기존_산출이_있으면_gen_무호출로_선다(tool_name, out_names,
                                             tmp_path: Path, monkeypatch):
    """★이 시험이 본체다.

    가드가 `main()` 안에서 **provider 를 만들기 전**에 불려야 한다.
    나중에 불리면 유료 호출이 이미 나간 뒤다.
    """
    mod = _load(tool_name)

    # 준비 단계(_preflight)는 실물 기록을 읽으러 가므로 세운다 — 재려는 것은
    # 「덮기 직전에 서는가」이지 프롬프트 조립이 아니다.
    monkeypatch.setattr(mod, "_preflight", lambda: ("old", "new", [], {}))
    monkeypatch.setattr(mod, "OUT", tmp_path / "exp")
    (tmp_path / "exp").mkdir()
    for n in out_names:
        (tmp_path / "exp" / n).write_bytes(b"x")

    called = {"n": 0}

    def _boom(*a, **k):
        called["n"] += 1
        raise AssertionError("★gen 이 불렸다 — 가드가 provider 뒤에 있다")

    import app.modules.pipeline.multiroll_gemini as mg

    monkeypatch.setattr(mg, "make_nb2_gen_fn", _boom)
    monkeypatch.setattr(sys, "argv", [tool_name, "--run"])

    with pytest.raises(SystemExit) as e:
        mod.main()
    assert "덮을 뻔했다" in str(e.value)
    assert called["n"] == 0, "provider 를 만든 뒤에 섰다"
