"""C2 2단계 (2026-07-02) — 단일 연속 프레임 VLM readback + correction 1회 재생성.

default-deny 게이트/no-op/재시도/비차단 실패 경로 전부 결정론(mock). 실 VLM
판정 품질은 canary(실 콜라주 vs 정상 프레임)+육안.
"""
from __future__ import annotations

from unittest.mock import MagicMock, patch

from app.core.config import settings
from app.modules.pipeline.scene_image_pipeline import (
    _SINGLE_FRAME_CORRECTION,
    _single_frame_readback_and_retry,
    _single_frame_verdict_violates,
)


# ─────────────────── verdict 게이트 (default-deny) ───────────────────


def test_violates_matrix():
    ok = {"is_single_continuous_frame": True, "has_panel_grid_or_collage": False,
          "confidence": "high"}
    assert _single_frame_verdict_violates(ok) is False
    assert _single_frame_verdict_violates(
        {**ok, "has_panel_grid_or_collage": True}) is True
    assert _single_frame_verdict_violates(
        {**ok, "is_single_continuous_frame": False}) is True
    # 저신뢰 위반은 재시도 안 함 (default-deny)
    assert _single_frame_verdict_violates(
        {**ok, "has_panel_grid_or_collage": True, "confidence": "low"}) is False
    assert _single_frame_verdict_violates({}) is False
    assert _single_frame_verdict_violates(None) is False  # type: ignore[arg-type]


# ─────────────────── readback 흐름 (mock) ───────────────────


def _run(monkeypatch, *, enabled=True, verdicts=None, gen_side_effect=None):
    monkeypatch.setattr(settings, "scene_single_frame_readback_enabled", enabled,
                        raising=False)
    client = MagicMock()
    if gen_side_effect:
        client.generate_image.side_effect = gen_side_effect
    else:
        client.generate_image.return_value = (b"retry-png", None)
    with patch(
        "app.modules.pipeline.scene_image_pipeline._call_gpt_lvm",
        side_effect=(verdicts or []),
    ) as lvm:
        img, prompt, diag = _single_frame_readback_and_retry(
            gemini_client=client, img=b"orig-png", current_prompt="PROMPT",
            labeled_refs=[("ref", b"r")], beat_title="beat",
            trace_meta=None,
        )
    return img, prompt, diag, client, lvm


def test_flag_off_noop(monkeypatch):
    img, prompt, diag, client, lvm = _run(monkeypatch, enabled=False)
    assert (img, prompt, diag) == (b"orig-png", "PROMPT", None)
    assert lvm.call_count == 0 and client.generate_image.call_count == 0


def test_clean_verdict_no_retry(monkeypatch):
    v = {"is_single_continuous_frame": True, "has_panel_grid_or_collage": False,
         "confidence": "high"}
    img, prompt, diag, client, lvm = _run(monkeypatch, verdicts=[v])
    assert img == b"orig-png" and prompt == "PROMPT"
    assert diag["checked"] is True and diag["violation"] is False
    assert client.generate_image.call_count == 0


def test_violation_retries_once_with_correction(monkeypatch):
    bad = {"is_single_continuous_frame": False, "has_panel_grid_or_collage": True,
           "confidence": "high"}
    good = {"is_single_continuous_frame": True, "has_panel_grid_or_collage": False,
            "confidence": "high"}
    img, prompt, diag, client, lvm = _run(monkeypatch, verdicts=[bad, good])
    assert img == b"retry-png"
    assert _SINGLE_FRAME_CORRECTION in prompt and prompt.startswith("PROMPT")
    assert diag["violation"] is True and diag["retried"] is True
    assert diag["resolved_after_retry"] is True
    assert client.generate_image.call_count == 1  # 연쇄 재시도 금지
    assert lvm.call_count == 2


def test_still_violating_after_retry_keeps_retry_and_diags(monkeypatch):
    bad = {"is_single_continuous_frame": False, "has_panel_grid_or_collage": True,
           "confidence": "medium"}
    img, prompt, diag, client, lvm = _run(monkeypatch, verdicts=[bad, bad])
    assert img == b"retry-png"  # correction 본 채택 + 진단만
    assert diag["resolved_after_retry"] is False
    assert client.generate_image.call_count == 1


def test_vlm_error_nonblocking(monkeypatch):
    img, prompt, diag, client, lvm = _run(
        monkeypatch, verdicts=[RuntimeError("lvm down")])
    assert img == b"orig-png" and prompt == "PROMPT"
    assert diag == {"checked": False, "error": "lvm down"}


def test_retry_generation_error_keeps_original(monkeypatch):
    bad = {"is_single_continuous_frame": False, "has_panel_grid_or_collage": True,
           "confidence": "high"}
    img, prompt, diag, client, lvm = _run(
        monkeypatch, verdicts=[bad], gen_side_effect=RuntimeError("gen down"))
    assert img == b"orig-png" and prompt == "PROMPT"
    assert diag["violation"] is True and diag["retried"] is False
    assert "retry_error" in diag
