"""plate_multiroll — 플레이트 공통 파이프 opt-in 분기 테스트."""
from __future__ import annotations

from pathlib import Path
from typing import Any, Dict, List

from app.modules.pipeline.plate_multiroll import (
    PLATE_REF_LABEL,
    render_plate_multiroll,
)


class FakeGen:
    def __init__(self):
        self.calls: List[Dict[str, Any]] = []

    def __call__(self, tag, prompt, labeled_refs, out_path: Path) -> Path:
        self.calls.append({"tag": tag, "refs": list(labeled_refs)})
        out_path.parent.mkdir(parents=True, exist_ok=True)
        out_path.write_bytes(f"img:{out_path.stem.rsplit('_', 1)[-1]}".encode())
        return out_path


def fake_judge(tag, prompt, labeled_refs, cand_paths, labels):
    return {
        "winner": "B",
        "ranking": ["B", "A", "C"],
        "verdicts": [
            {"label": lab, "score": sc, "verdict_ko": ""}
            for lab, sc in [("A", 5), ("B", 9), ("C", 7)]
        ],
    }


def _info():
    return {
        "status": "failed", "attempts": 0, "strategies": [],
        "ref_used": "refs_2", "final_block_reason": None,
    }


def test_multiroll_plate_selects_and_labels(tmp_path):
    ref1 = tmp_path / "fp.png"
    ref1.write_bytes(b"fp")
    gen = FakeGen()
    info = render_plate_multiroll(
        prompt="PLATE PROMPT",
        ref_paths=[ref1],
        out_path=tmp_path / "bg_x.png",
        bg_id="bg_x",
        info=_info(),
        roll_count=3,
        critique_enabled=False,
        gen_fn=gen,
        judge_fn=fake_judge,
    )
    assert info["status"] == "ok"
    assert Path(info["png_path"]).read_bytes() == b"img:b"  # 선정본이 최종
    assert info["multiroll"]["selected"] == "B"
    # 라벨 부여 확인
    assert gen.calls[0]["refs"][0][0] == PLATE_REF_LABEL


def test_multiroll_plate_critique_fix_is_final(tmp_path):
    ref1 = tmp_path / "fp.png"
    ref1.write_bytes(b"fp")
    gen = FakeGen()

    def crit(tag, prompt, labeled_refs, image_path):
        return {"issues": [{"issue_ko": "x", "fix_en": "Fix x."}]}

    # 재판정이 기본 ON(2026-08-06)이 되면서 대역이 없으면 실제 판정 API 를
    # 때린다. 여기서는 "수정본이 최종"을 보는 테스트이므로 수정본(B)을 승자로
    # 고정한다 — 정순은 B, 역순은 A 가 수정본 자리다.
    _rj_calls = []

    def rejudge(tag, prompt, labeled_refs, cand_paths, labels):
        _rj_calls.append(tag)
        # 제시 순서상 수정본이 놓인 자리: 정순=B, 역순=A
        win = "A" if tag.endswith("_rev") else "B"
        lose = "B" if win == "A" else "A"
        return {
            "winner": win, "ranking": [win, lose],
            "verdicts": [
                {"label": win, "score": 9, "verdict_ko": "ok"},
                {"label": lose, "score": 3, "verdict_ko": "no"},
            ],
        }

    info = render_plate_multiroll(
        fix_rejudge_fn=rejudge,
        prompt="PLATE PROMPT",
        ref_paths=[ref1],
        out_path=tmp_path / "bg_x.png",
        bg_id="bg_x",
        info=_info(),
        roll_count=3,
        critique_enabled=True,
        gen_fn=gen,
        judge_fn=fake_judge,
        critique_fn=crit,
    )
    assert info["status"] == "ok"
    assert Path(info["png_path"]).read_bytes() == b"img:fix"
    assert info["multiroll"]["fix_applied"] is True


def test_failure_isolated_to_node(tmp_path):
    def boom(tag, prompt, labeled_refs, out_path):
        raise RuntimeError("engine down")

    info = render_plate_multiroll(
        prompt="P",
        ref_paths=[],
        out_path=tmp_path / "bg_y.png",
        bg_id="bg_y",
        info=_info(),
        roll_count=2,
        critique_enabled=False,
        gen_fn=boom,
        judge_fn=fake_judge,
    )
    assert info["status"] == "failed"
    assert "engine down" in info["final_block_reason"]


def test_flag_off_keeps_legacy_renderer(monkeypatch, tmp_path):
    """plate_multiroll_enabled=False 면 기존 gpt 경로 유지 (호출 검증)."""
    from app.core.config import settings
    from app.modules.pipeline import background_render as br

    monkeypatch.setattr(settings, "plate_multiroll_enabled", False)
    called = {}

    def fake_gpt(*a, **kw):
        called["gpt"] = True
        return b"png-bytes"

    monkeypatch.setattr(br, "call_gpt_image_bytes", fake_gpt)
    monkeypatch.setattr(br, "reserve_current_call", lambda **kw: None)
    info = br.render_one_background(
        openai_client=object(),
        image_model="gpt-image-2",
        prompt="P",
        out_path=tmp_path / "bg.png",
        fp_path=None,
        prior_bg_paths=[],
        bg_id="bg",
        max_attempts=1,
    )
    assert called.get("gpt") is True
    assert info["status"] == "ok"


def test_flag_on_routes_to_multiroll(monkeypatch, tmp_path):
    from app.core.config import settings
    from app.modules.pipeline import background_render as br
    from app.modules.pipeline import plate_multiroll as pm

    monkeypatch.setattr(settings, "plate_multiroll_enabled", True)
    routed = {}

    def fake_render(**kw):
        routed["kw"] = kw
        kw["info"]["status"] = "ok"
        return kw["info"]

    monkeypatch.setattr(pm, "render_plate_multiroll", fake_render)
    info = br.render_one_background(
        openai_client=object(),
        image_model="gpt-image-2",
        prompt="P",
        out_path=tmp_path / "bg.png",
        fp_path=None,
        prior_bg_paths=[],
        bg_id="bg",
    )
    assert info["status"] == "ok"
    assert routed["kw"]["bg_id"] == "bg"


def test_fix_rejudge_flag_wires_2cand_judge(monkeypatch, tmp_path):
    """E2E10 fix②: flag ON → 재판정으로 원본 유지 가능 (개악 차단)."""
    from app.core.config import settings
    import app.modules.pipeline.multiroll_gemini as mg

    monkeypatch.setattr(settings, "multiroll_fix_rejudge_enabled", True)

    def rejudge(tag, prompt, labeled_refs, cand_paths, labels):
        # 원본(선정 B롤) 바이트 후보를 항상 승자로 — flip 양회 합의
        idx = next(
            i for i, p in enumerate(cand_paths)
            if Path(p).read_bytes() == b"img:b"
        )
        win = labels[idx]
        return {
            "winner": win,
            "ranking": sorted(labels, key=lambda l: l != win),
            "verdicts": [
                {"label": l, "score": 9 if l == win else 4,
                 "verdict_ko": ""}
                for l in labels
            ],
        }

    monkeypatch.setattr(
        mg, "make_gemini_judge_fn", lambda **kw: rejudge)

    ref = tmp_path / "fp.png"
    ref.write_bytes(b"fp")
    out = tmp_path / "plate.png"
    info = render_plate_multiroll(
        prompt="P", ref_paths=[ref], out_path=out, bg_id="B01",
        info=_info(), roll_count=3, critique_enabled=True,
        gen_fn=FakeGen(),
        judge_fn=fake_judge,
        critique_fn=lambda *a: {"issues": [{"fix_en": "x"}]},
    )
    assert info["status"] == "ok"
    assert info["multiroll"]["fix_rejudge_won"] is False
    assert out.read_bytes() == b"img:b"  # 수정본 개악 → 선정 원본 유지


def test_fix_rejudge_flag_off_keeps_fix_final(monkeypatch, tmp_path):
    from app.core.config import settings

    monkeypatch.setattr(settings, "multiroll_fix_rejudge_enabled", False)
    ref = tmp_path / "fp.png"
    ref.write_bytes(b"fp")
    out = tmp_path / "plate.png"
    info = render_plate_multiroll(
        prompt="P", ref_paths=[ref], out_path=out, bg_id="B01",
        info=_info(), roll_count=3, critique_enabled=True,
        gen_fn=FakeGen(),
        judge_fn=fake_judge,
        critique_fn=lambda *a: {"issues": [{"fix_en": "x"}]},
    )
    assert info["status"] == "ok"
    assert "fix_rejudge_won" not in info["multiroll"]
    assert out.read_bytes() == b"img:fix"


def test_fix_rejudge_header_is_neutral_and_wired(monkeypatch, tmp_path):
    """Codex HIGH-4 — 재판정 헤더=provenance 비노출 중립 계약(팩 v3)."""
    from app.core.config import settings
    import app.modules.pipeline.multiroll_gemini as mg

    header = mg.load_fix_rejudge_header()
    assert "generated from this" not in header  # 기본 헤더 문구 금지
    assert "do not assume" in header

    monkeypatch.setattr(settings, "multiroll_fix_rejudge_enabled", True)
    captured = {}
    real = mg.make_gemini_judge_fn

    def spy(**kw):
        captured.update(kw)
        return lambda *a, **k: {
            "winner": "A", "ranking": ["A", "B"],
            "verdicts": [
                {"label": "A", "score": 9, "verdict_ko": ""},
                {"label": "B", "score": 4, "verdict_ko": ""},
            ],
        }

    monkeypatch.setattr(mg, "make_gemini_judge_fn", spy)
    ref = tmp_path / "fp.png"
    ref.write_bytes(b"fp")
    render_plate_multiroll(
        prompt="P", ref_paths=[ref], out_path=tmp_path / "plate.png",
        bg_id="B01", info=_info(), roll_count=3, critique_enabled=True,
        gen_fn=FakeGen(), judge_fn=fake_judge,
        critique_fn=lambda *a: {"issues": [{"fix_en": "x"}]},
    )
    assert captured.get("prompt_header") == header
    _ = real  # noqa: F841
