"""최종 i2i 로 나가는 **두 스템이 다 지문에 접히는가** (2026-08-27, 과제 #94).

## 무엇이 문제였나

재료를 넘기는 판은 문안 **두 조각**을 이어 붙여 보낸다:

    build_cine_transform_prompt
      ├─ cine_transform          (base)
      └─ cine_stage_direction    (재료 절 — CAMERA·LIGHTING 을 실어 준다)

그런데 outer 지문은 **앞엣것만** 접었다. 뒤엣것의 문안을 고치면 나가는 글은
바뀌는데 지문이 안 움직여 **완주한 샷이 옛 그림 그대로 통과**한다.

★팩 selector 만 접는 것으로는 못 잡는다 — **같은 팩 안에서 문안을 고치는
 것이 흔한 일**이다(이 판에서만 세 번 그랬다).

## 재는 자리

「스템 이름을 불렀나」가 아니라 **지문 값이 실제로 움직이나**를 본다.
이름만 세면 다른 경로로 그 값이 안 접혀도 통과한다.
"""
from __future__ import annotations

import pytest

import app.modules.pipeline.still_recipe as sr
from app.modules.pipeline.cine_transform import (
    CINE_STAGE_DIRECTION_STEM,
    CINE_TRANSFORM_STEM,
)


def _hash(monkeypatch, *, stage_dir_on: bool, doctored=None) -> str:
    from app.core.config import settings
    from app.core.steps.image_steps import SceneImagePipelineStep

    step = SceneImagePipelineStep.__new__(SceneImagePipelineStep)
    step.project_config = {}
    for k, v in (("still_recipe_mode", "v1"),
                 ("still_cine_transform_enabled", True),
                 ("still_cine_stage_direction_enabled", stage_dir_on)):
        monkeypatch.setattr(settings, k, v)
    real = _hash.real

    def _spy(selector, stem):
        return "CHANGED" if doctored and stem == doctored else real(
            selector, stem)

    monkeypatch.setattr(sr, "recipe_stem_content_hash", _spy)
    return step._config_hash_base()


# ★원본을 **한 번만** 잡는다 — 헬퍼 안에서 매번 잡으면 두 번째부터
#  「이미 씌운 대역」을 원본으로 잡아 사슬이 겹치고 변조가 묻힌다
#  (2026-08-27 실측: 그래서 양성 대조 절반이 거짓 초록이었다).
_hash.real = sr.recipe_stem_content_hash


def test_both_stems_move_the_hash(monkeypatch):
    """★★**나가는 문안 두 조각이 다 지문에 있어야** 한다."""
    base = _hash(monkeypatch, stage_dir_on=True)
    for stem in (CINE_TRANSFORM_STEM, CINE_STAGE_DIRECTION_STEM):
        got = _hash(monkeypatch, stage_dir_on=True, doctored=stem)
        assert got != base, (
            f"{stem} 문안을 고쳤는데 지문이 그대로다 — "
            "나가는 글은 바뀌는데 옛 그림이 남는다")


def test_the_material_stem_is_not_folded_when_it_does_not_go_out(monkeypatch):
    """★안 나가는 문안을 접으면 **헛돈**이다.

    재료를 안 넘기는 판은 `cine_stage_direction` 절이 아예 안 붙는다
    (`build_cine_transform_prompt` 가 재료가 없으면 base 만 돌려준다).
    그 문안을 고칠 때 완주한 샷이 다시 사면 안 된다.
    """
    base = _hash(monkeypatch, stage_dir_on=False)
    got = _hash(monkeypatch, stage_dir_on=False,
                doctored=CINE_STAGE_DIRECTION_STEM)
    assert got == base, (
        "재료를 안 넘기는 판이 그 스템을 접고 있다 — 헛되이 다시 산다")


def test_turning_the_material_on_moves_the_hash(monkeypatch):
    """재료를 켜면 나가는 글이 늘어난다 — 지문도 달라야 한다."""
    assert _hash(monkeypatch, stage_dir_on=False) != _hash(
        monkeypatch, stage_dir_on=True)


def test_the_stem_names_match_what_the_assembly_loads():
    """★상수와 **실제로 로드하는 이름**이 갈리면 지문이 엉뚱한 것을 접는다."""
    import pathlib

    src = (pathlib.Path(__file__).resolve().parents[2] / "app" / "modules"
           / "pipeline" / "still_recipe.py").read_text(encoding="utf-8")
    for stem in (CINE_TRANSFORM_STEM, CINE_STAGE_DIRECTION_STEM):
        assert f'"{stem}"' in src, (
            f"조립이 {stem} 를 안 로드한다 — 지문이 없는 것을 접는다")


def test_the_material_pack_actually_has_the_stem():
    """★없는 스템을 접으면 그 자리에서 죽는다 — 전제를 확인한다."""
    import pathlib

    from app.modules.pipeline.still_recipe import (
        CINE_STAGE_DIRECTION_PROMPT_VERSION,
        resolve_prompt_version,
    )
    from app.modules.prompt_loader import PROMPTS_BASE

    d = (pathlib.Path(PROMPTS_BASE) / "still_recipe"
         / resolve_prompt_version(CINE_STAGE_DIRECTION_PROMPT_VERSION))
    assert (d / f"{CINE_STAGE_DIRECTION_STEM}.md").exists(), (
        f"{d.name} 에 재료 절 스템이 없다")


if __name__ == "__main__":
    raise SystemExit(pytest.main([__file__]))
