"""정적 절 셋이 상위 계약을 누르거나 없는 것을 만들던 것 (감사 P1-A).

## 무엇이 결함이었나

전 후보 2,551개 중 `REALIZE FIGURATIVE`·`EXPRESSIONS ARE ACTED` 는 각각
**2,537(99.5%)**, `HUMAN FORM` 은 2,167, `NATURAL PERFORMANCE` 는 1,610 에
실린다. 넷 다 「언제나 나가는 정적 문안」인데:

    ⓐ REALIZE  비유를 "(lighting, distance, angle, wardrobe)" 로 실현하라 했다.
              그 넷은 LIGHTING & MOOD · CAMERA & FRAME · 아웃룩이 소유한다.
              실측 CAMERA 권위와 공존 1,600후보 · **798 채택**.
    ⓑ HUMAN    "always render the human form to the **maximum extent**" 가
              close-up(520후보·260채택)·insert(106·53)와 맞섰다.
    ⓒ NATURAL  "hands engaged with **something real**" 이 소품이 없어도
              손에 뭘 쥐게 했다.
    ⓓ EXPR     사람 눈·얼굴 얘기인데 **무인샷에도 조건 없이** 붙었다.
              바로 위 `human_form` 은 1-F(2026-08-27)로 게이팅됐는데 이
              줄만 빠졌다. 실측 242후보(119 채택).

## 이 시험이 재는 것

`build_still_prompt` 가 내놓는 **조립 완문**을 잰다 — 스템 파일을 직접
읽지 않는다. 스템만 읽으면 조립이 다른 팩으로 옮겨 가도 초록으로 남는다.
두 백엔드 갈래(nb2/grok)를 따로 태운다.
"""
from __future__ import annotations

import pytest

from app.modules.pipeline import still_recipe as sr

# 조립 인자 — 두 갈래에 똑같이 넣는다. 작품 고유명사는 쓰지 않는다.
# ★형제 시험 파일에서 import 하지 않는다 — 수집 오류 하나가 디렉토리를
#  통째로 죽인다(`tests/core` 가 몇 달간 안 돌던 자리).
COMMON = dict(
    shot_desc=(
        "A middle-aged man in a dark jacket extends his forearm holding an "
        "open notebook diagonally toward a younger man in a shirt across a "
        "narrow shared desk edge."
    ),
    place_text="detective office workspace, interior",
    time_of_day_en="night",
    world_anchor=" — contemporary, 2016",
    bg_only=False,
    prev_used=True,
    prev_usage_en="continue from the previous frame",
    pose_clauses=["C01 seated, forearm extended"],
    movement_en="C01 holds the notebook steady",
    figures_en="two figures, upper-torso framing",
    carried_en="open notebook",
    handled_by="C01",
    char_names=["C01 (middle-aged man, dark jacket)", "C02 (young man, shirt)"],
    camera_frame_en="Insert framing from upper-torso height, three-quarter angle",
    lighting_mood_en="subdued neutral-to-cool nighttime ambient",
    identity_role_en="",
    signage_en="",
    prompt_version="1",
)

BACKENDS = ["", sr.STILL_COMPACT_PROMPT_VERSION]

# 상위 계약을 누르거나 없는 것을 만들던 옛 어구 — 다시 들어오면 걸린다.
BANNED = (
    "lighting, distance, angle",      # ⓐ 축 이름을 이 절이 소유하던 것
    "to the maximum extent",          # ⓑ 프레이밍과 맞서던 것
    "hands engaged with something real",   # ⓒ 없는 소품을 부르던 것
)

# 새 문안이 **말해야** 하는 것 — 금지만 남기면 그릴 재료가 없다.
MUST_SAY = (
    "within whatever this brief already fixes",
    "as fully as the framing shows",
    "hands naturally positioned for the action",
)

NO_PEOPLE = "NO PEOPLE IN THIS SHOT"
HUMAN_FORM = "EVERY CHARACTER IS A HUMAN BEING"
EXPRESSION = "EXPRESSIONS ARE ACTED"


def _prompt(**over) -> str:
    kw = dict(COMMON)
    kw.setdefault("conduct_version", sr.STILL_CONDUCT_PROMPT_VERSION)
    guidance = over.pop("guidance_version", "")
    kw.update(over)
    return sr.build_still_prompt(guidance_version=guidance, **kw)


@pytest.mark.parametrize("guidance", BACKENDS)
def test_old_authority_grabbing_wording_is_gone(guidance):
    p = _prompt(guidance_version=guidance)
    for phrase in BANNED:
        assert phrase not in p, (
            f"guidance={guidance!r} 조립에 옛 어구가 남아 있다: {phrase!r}")


@pytest.mark.parametrize("guidance", BACKENDS)
def test_new_wording_actually_lands(guidance):
    """★스템만 고치고 조립이 옛 팩을 읽으면 여기서 걸린다."""
    p = _prompt(guidance_version=guidance)
    for phrase in MUST_SAY:
        assert phrase in p, (
            f"guidance={guidance!r} 조립에 새 문안이 안 실렸다: {phrase!r}")


@pytest.mark.parametrize("guidance", BACKENDS)
def test_expression_clause_is_absent_from_people_less_shots(guidance):
    """사람이 안 그려지는 샷에 눈·얼굴 절이 붙으면 안 된다 (ⓓ)."""
    p = _prompt(guidance_version=guidance, bg_only=True, char_names=[])
    assert NO_PEOPLE in p, "이 조립은 무인샷이어야 한다"
    assert EXPRESSION not in p, "무인샷에 표정 절이 붙었다"
    assert HUMAN_FORM not in p, "무인샷에 인물 실체 절이 붙었다"


@pytest.mark.parametrize("guidance", BACKENDS)
def test_both_clauses_return_when_the_shot_names_people(guidance):
    """`char_names` 가 있으면 `bg_only` 여도 둘 다 나간다 (2026-08-06 계약)."""
    p = _prompt(guidance_version=guidance, bg_only=True)
    assert EXPRESSION in p
    assert HUMAN_FORM in p


@pytest.mark.parametrize("guidance", BACKENDS)
def test_people_shot_keeps_all_three_clauses(guidance):
    p = _prompt(guidance_version=guidance)
    assert EXPRESSION in p
    assert HUMAN_FORM in p
    assert "NATURAL PERFORMANCE" in p


@pytest.mark.parametrize(
    "grok_backend,expected", [(False, "39"), (True, "40")])
def test_naturalism_pack_follows_the_backend(grok_backend, expected):
    """★이 절은 realize 쌍과 **다른 팩**이다 — 한쪽만 고쳐도 다른 쪽 팩이
    같이 움직여 무관한 재생성이 나는 것을 막는다."""
    sel = sr.still_guidance_selector(grok_backend)
    assert sr.naturalism_pack(sel) == expected
    assert expected not in (sr.REALIZE_PROMPT_VERSION,
                            sr.REALIZE_COMPACT_PROMPT_VERSION)


def test_shed_table_points_at_the_pack_the_assembly_renders():
    """덜어내기가 조립과 **다른 팩**을 보면 아무것도 못 지운다.

    문자열이 안 맞으면 상한을 넘은 롤이 그대로 죽는다 — 그래서 표가
    조립 selector 를 그대로 가리켜야 한다.
    """
    assert (sr.GROK_SHED_STEM_SELECTOR["realize_still"]
            == sr.REALIZE_COMPACT_PROMPT_VERSION)
    assert (sr.GROK_SHED_STEM_SELECTOR["naturalism_clause"]
            == sr.naturalism_pack(sr.still_guidance_selector(True)))


def test_fingerprint_folds_the_naturalism_stem_bytes():
    """지문이 이 절의 **바이트**를 접어야 고친 문안이 그림에 닿는다.

    종전엔 `recipe_conduct_pack`(팩 문자열)만 접혀서 바이트를 고쳐도
    완주 샷이 clean skip 했다.
    """
    import ast
    import pathlib

    src = pathlib.Path(
        __file__).resolve().parents[2] / "app/core/steps/image_steps.py"
    tree = ast.parse(src.read_text(encoding="utf-8"))
    names = {
        n.name for node in ast.walk(tree)
        if isinstance(node, ast.ImportFrom)
        and node.module == "app.modules.pipeline.still_recipe"
        for n in node.names
    }
    assert "naturalism_pack" in names, (
        "image_steps 가 naturalism 팩을 조립과 같은 함수로 안 고른다")
    text = src.read_text(encoding="utf-8")
    assert "recipe_naturalism_content" in text, (
        "naturalism 스템 바이트가 지문에 안 접힌다")
