"""Patch B-min — scene_image_pipeline 의 semantic_constraints propagation 검증.

Gemini moderation block → sanitizer 호출 시 semantic_constraints 가 동일 인자로
전달되는지만 검증. PNG metadata + LVM 은 본 patch 무관 — monkeypatch 우회.

시나리오 의존 0: synthetic identifier (C91 / dead) 만 사용.
"""
from pathlib import Path

from app.modules.pipeline.scene_image_pipeline import generate_and_validate_scene
from app.modules.llm.gemini_image_client import ModerationError


def test_pipeline_propagates_constraints_to_sanitizer(tmp_path: Path, monkeypatch):
    captured_calls: list[dict] = []

    class FakeSanitizer:
        def sanitize(self, *args, **kwargs):
            captured_calls.append(kwargs)
            return {
                "sanitized_prompt": "sanitized output",
                "changes": "removed",
                "strategy": "film_previs",
            }

    class FakeGeminiClient:
        def __init__(self):
            self._attempt = 0
        def set_context(self, **kw): pass
        def generate_image(self, prompt, labeled_references=None, aspect_ratio=None):
            self._attempt += 1
            if self._attempt == 1:
                raise ModerationError(
                    block_reason="SAFETY",
                    block_categories=["violence"],
                    raw_response={},
                )
            # PNG metadata embed 가 monkeypatch 되므로 dummy bytes 안전.
            return (b"\x89PNG\r\n\x1a\n_dummy_png_bytes_for_test_", None)

    # PromptSanitizer 클래스 자체를 source module 에서 monkeypatch
    # (scene_image_pipeline.py:241 는 function-local import 이므로 source module 패치가 정확).
    monkeypatch.setattr(
        "app.modules.prompt_sanitizer.PromptSanitizer",
        lambda *a, **kw: FakeSanitizer(),
    )
    # PNG metadata embed 우회 (function-local import → source module 패치).
    monkeypatch.setattr(
        "app.modules.png_metadata.embed_png_metadata",
        lambda *a, **kw: True,
    )
    # LVM validation skip (module-level → consumer path 패치 OK).
    monkeypatch.setattr(
        "app.modules.pipeline.scene_image_pipeline._decide_scene_lvm",
        lambda *a, **kw: (False, "test_skip"),
    )

    constraints = {
        "semantic_mode": "immobilized",
        "entity_ids": ["C91"],
        "source_states": {"C91": "dead"},
        "override_strategy_prefix": True,
        "preserve_pose": True,
        "preserve_subject_state": True,
        "forbid_state_polarity_rewrite": True,
        "forbid_unharmed_rewrite": True,
        "forbid_active_reaction": True,
        "evidence": [],
    }

    generate_and_validate_scene(
        gemini_client=FakeGeminiClient(),
        t2i_prompt="prompt",
        beat_title="beat",
        output_dir=tmp_path,
        semantic_constraints=constraints,
    )

    assert len(captured_calls) == 1
    assert captured_calls[0]["semantic_constraints"] == constraints
    # 코드: for attempt in range(4); 1차 ModerationError → sanitize(attempt=loop_idx+1=1, film_previs).
    assert captured_calls[0]["attempt"] == 1


def test_pipeline_no_constraints_default(tmp_path: Path, monkeypatch):
    """semantic_constraints 미전달 → sanitizer 도 None 으로 받음 (기존 동작 보존)."""
    captured_calls: list[dict] = []

    class FakeSanitizer:
        def sanitize(self, *args, **kwargs):
            captured_calls.append(kwargs)
            return {"sanitized_prompt": "x", "changes": "", "strategy": "film_previs"}

    class FakeGeminiClient:
        def __init__(self):
            self._attempt = 0
        def set_context(self, **kw): pass
        def generate_image(self, prompt, labeled_references=None, aspect_ratio=None):
            self._attempt += 1
            if self._attempt == 1:
                raise ModerationError(
                    block_reason="SAFETY",
                    block_categories=["violence"],
                    raw_response={},
                )
            return (b"\x89PNG\r\n\x1a\ndummy", None)

    monkeypatch.setattr("app.modules.prompt_sanitizer.PromptSanitizer", lambda *a, **kw: FakeSanitizer())
    monkeypatch.setattr("app.modules.png_metadata.embed_png_metadata", lambda *a, **kw: True)
    monkeypatch.setattr("app.modules.pipeline.scene_image_pipeline._decide_scene_lvm", lambda *a, **kw: (False, "test_skip"))

    generate_and_validate_scene(
        gemini_client=FakeGeminiClient(),
        t2i_prompt="prompt",
        beat_title="beat",
        output_dir=tmp_path,
    )

    assert captured_calls[0].get("semantic_constraints") is None
