"""연출 재료를 최종 i2i 로 (2026-08-25 사용자 지시 "카메라 앵글이나 기타
시네마틱 관련 프롬프트 추가하는 것으로 해, 이전에는 하지말고") — 계약 시험.

무엇을 지키나:
  · 앞 단계에서 빠지는 것은 **CAMERA 산문 한 줄**뿐이다. 구도(거리와 잘림·
    화면 배치·시각 관계)는 남는다 — 사용자 판정이 「카메라 절 없는 쪽이 훨씬
    좋다, 그런데 구도가 안 좋다」였고 그 구도를 잡던 것이 같은 절의 다른
    줄이기 때문이다.
  · **조명 절은 앞 단계에서 안 뺀다.** 빼면 E2E10 이 실측한 평면 낙서화
    (S12sh18·S18sh10·S25sh5)가 되살아난다. 최종 i2i 는 같은 재료를 받아
    빛이 **어떻게 행동하는가**만 손댄다.
  · OFF 면 문안이 종전과 **바이트 동일**이고 팩 selector 도 안 움직인다 —
    selector 는 스텝 지문에 들어 있어 올리는 순간 완주본이 재실행된다.
"""
from __future__ import annotations

import pytest

from app.core.config import settings


@pytest.fixture(autouse=True)
def _pin_baseline(monkeypatch):
    """★기계의 `.env` 를 물지 않게 기준선을 못박는다 — 지금 이 기계는
    연출 재료가 켜져 있다. 켜는 것을 재는 시험은 자기 안에서 다시 켠다."""
    monkeypatch.setattr(
        settings, "still_cine_stage_direction_enabled", False,
        raising=False)

_STAGING = {
    "camera_direction": "low angle, waist height, three steps back",
    "key_bg_elements": [
        {"element": "workbench", "state": "cluttered",
         "camera_use": "foreground anchor"},
    ],
    "lighting_mood": "one bare bulb overhead, hard shadows underneath",
}


# ── 팩 selector: 올리는 순간 완주본이 재실행된다 ────────────────────


def test_selector_stays_on_legacy_pack_when_off(monkeypatch):
    """★**코드 기본값**을 본다 — 실행 중 `settings` 는 이 기계의 `.env` 가
    정하므로(지금 켜짐) 그것을 보면 「무엇이 기본인가」가 아니라 「이 기계가
    어떻게 설정됐나」를 재게 된다."""
    from app.core.config import Settings
    from app.modules.pipeline.still_recipe import (
        CINE_TRANSFORM_PROMPT_VERSION, cine_pack_selector,
    )

    assert Settings.model_fields[
        "still_cine_stage_direction_enabled"].default is False
    monkeypatch.setattr(settings, "still_cine_stage_direction_enabled", False,
                        raising=False)
    assert cine_pack_selector() == CINE_TRANSFORM_PROMPT_VERSION


def test_selector_moves_to_v24_when_on(monkeypatch):
    from app.modules.pipeline.still_recipe import (
        CINE_STAGE_DIRECTION_PROMPT_VERSION, cine_pack_selector,
    )

    monkeypatch.setattr(settings, "still_cine_stage_direction_enabled", True,
                        raising=False)
    assert cine_pack_selector() == CINE_STAGE_DIRECTION_PROMPT_VERSION


def test_v24_transform_stem_is_byte_identical_to_v23():
    """본문은 **안 건드린다** — v24 가 더하는 것은 재료 절뿐이다.

    본문까지 손대면 재료 실험의 결과가 「재료 덕분인지 본문 덕분인지」
    갈리지 않는다.
    """
    from app.modules.prompt_loader import PROMPTS_BASE
    from app.modules.pipeline.still_recipe import (
        CINE_STAGE_DIRECTION_PROMPT_VERSION, CINE_TRANSFORM_PROMPT_VERSION,
        resolve_prompt_version,
    )

    base = PROMPTS_BASE / "still_recipe"
    old = (base / resolve_prompt_version(CINE_TRANSFORM_PROMPT_VERSION)
           / "cine_transform.md").read_text(encoding="utf-8")
    new = (base / resolve_prompt_version(CINE_STAGE_DIRECTION_PROMPT_VERSION)
           / "cine_transform.md").read_text(encoding="utf-8")
    assert old == new


# ── 최종 i2i 문안: 재료가 없으면 종전 그대로 ────────────────────────


def test_prompt_without_material_is_unchanged():
    from app.modules.pipeline.still_recipe import build_cine_transform_prompt

    assert (build_cine_transform_prompt()
            == build_cine_transform_prompt(camera_direction_en="",
                                           lighting_mood_en=""))


def test_material_appends_stage_direction_clause():
    from app.modules.pipeline.still_recipe import (
        CINE_STAGE_DIRECTION_PROMPT_VERSION, build_cine_transform_prompt,
    )

    base = build_cine_transform_prompt(CINE_STAGE_DIRECTION_PROMPT_VERSION)
    out = build_cine_transform_prompt(
        CINE_STAGE_DIRECTION_PROMPT_VERSION,
        camera_direction_en=_STAGING["camera_direction"],
        lighting_mood_en=_STAGING["lighting_mood"],
    )
    assert out.startswith(base), "본문은 그대로 앞에 온다"
    assert f"- CAMERA: {_STAGING['camera_direction']}" in out
    assert f"- LIGHTING & MOOD: {_STAGING['lighting_mood']}" in out
    assert len(out) > len(base)


def test_only_the_material_that_exists_is_written():
    """빈 재료를 빈 줄로 내보내지 않는다 — 「CAMERA:」만 있고 값이 없으면
    모델이 그 자리를 지어 채운다."""
    from app.modules.pipeline.still_recipe import (
        CINE_STAGE_DIRECTION_PROMPT_VERSION, build_cine_transform_prompt,
    )

    out = build_cine_transform_prompt(
        CINE_STAGE_DIRECTION_PROMPT_VERSION,
        camera_direction_en=_STAGING["camera_direction"],
        lighting_mood_en="   ",
    )
    assert "- CAMERA:" in out
    assert "- LIGHTING & MOOD:" not in out


def test_stage_direction_gives_camera_the_viewpoint_not_the_content():
    """재료를 준다고 권위까지 넘기지 않는다 — 문안이 그것을 말해야 한다."""
    from app.modules.pipeline.still_recipe import (
        CINE_STAGE_DIRECTION_PROMPT_VERSION, resolve_prompt_version,
    )
    from app.modules.prompt_loader import PROMPTS_BASE

    text = (PROMPTS_BASE / "still_recipe"
            / resolve_prompt_version(CINE_STAGE_DIRECTION_PROMPT_VERSION)
            / "cine_stage_direction.md").read_text(encoding="utf-8")
    low = text.lower()
    # ★본문이 먼저 준 자유를 **명시적으로 덮어야** 한다. 이 저장소의 실측
    #  (팩 v23): 한 문단이 자기모순이면 **허가 쪽이 이긴다**. 본문의
    #  「you may change the camera angle…」이 그대로 남아 있으므로 재료 절이
    #  그것을 대체한다고 말해야 재료가 진다.
    assert "replaces the freedom given above" in low
    # 카메라는 어디서 보는가만 정한다
    assert "never what is there to see" in low
    # 광원의 존재는 소스가 이긴다 — 재료는 빛의 행동만 말한다
    assert "never tells you that a light exists" in low
    assert "the source still decides" in low


# ── 앞 단계: 빠지는 것은 CAMERA 한 줄뿐이다 ─────────────────────────


def test_omit_drops_only_the_camera_line():
    from app.modules.pipeline.still_recipe import build_camera_frame_clause

    kept = build_camera_frame_clause(_STAGING)
    trimmed = build_camera_frame_clause(_STAGING, omit_camera_direction=True)

    assert f"- CAMERA: {_STAGING['camera_direction']}" in kept
    assert "- CAMERA:" not in trimmed
    # ★구도는 남는다 — 이것이 빠지면 사용자가 지적한 「구도가 안 좋다」가 된다
    assert "KEY BACKGROUND ELEMENTS" in kept
    assert "KEY BACKGROUND ELEMENTS" in trimmed
    assert "workbench" in trimmed


def test_omit_is_inert_when_there_is_no_camera_direction():
    from app.modules.pipeline.still_recipe import build_camera_frame_clause

    staging = {k: v for k, v in _STAGING.items() if k != "camera_direction"}
    assert (build_camera_frame_clause(staging)
            == build_camera_frame_clause(staging, omit_camera_direction=True))


def test_default_call_is_byte_identical():
    """기본 인자로 부르면 종전과 같은 결과 — OFF 에서 byte-identical."""
    from app.modules.pipeline.still_recipe import build_camera_frame_clause

    assert (build_camera_frame_clause(_STAGING)
            == build_camera_frame_clause(
                _STAGING, omit_camera_direction=False))


def test_lighting_clause_is_not_removed_from_upstream():
    """★조명은 앞 단계에서 **안 뺀다** — 빼면 평면 낙서화가 되살아난다
    (E2E10 S12sh18·S18sh10·S25sh5 실측). 걷는 스위치 자체가 없어야 한다."""
    import inspect

    from app.modules.pipeline.still_recipe import build_lighting_mood_clause

    sig = inspect.signature(build_lighting_mood_clause)
    assert "omit" not in " ".join(sig.parameters), (
        "조명 절에 걷는 인자가 생겼다 — 앞 단계에서 빼면 안 된다")
    out = build_lighting_mood_clause(_STAGING)
    assert _STAGING["lighting_mood"] in out
