"""씬 단위 추측 인물 목록에는 배타 조항을 붙이지 않는다 (팩 v14).

실측 S11sh3: 샷 VE 가 비어 `ve_ids_for_shot` 이 같은 씬 옆 샷의 `김지후` 를
끌어왔고, 그것이 "must be one of: 김지후 — never anyone else" 로 나갔다.
샷 텍스트는 `정인우` 였으므로, 정인우가 제대로 그려진 후보 A·B 가 두 심판
모두에게 "허용 인물 목록에 없는 인물" hard violation 으로 탈락하고 검은
일반차·다른 인물 후보가 확정됐다. 255 선택 샷 중 이 경로 36개(14%).

확정 배정일 때는 배타 조항이 옳으므로 기존 문구를 그대로 쓴다 — 그 경우
출력이 이전과 **바이트 동일**해야 한다.
"""
import pytest

from app.modules.pipeline.still_recipe import (
    build_still_prompt,
    ve_ids_for_shot,
    ve_ids_for_shot_ex,
)

BASE = dict(
    shot_desc="열린 운전석 문 밖으로 몸을 기울인 정인우.",
    place_text="다세대 주택가 골목",
    time_of_day_en="night",
    bg_only=False,
    prev_used=False,
)


def _prompt(**kw):
    return build_still_prompt(**{**BASE, **kw})


# ── ve_ids_for_shot_ex: 확정/추측 구분 ────────────────────────────────

def test_shot_ve_present_is_exact():
    ids, exact = ve_ids_for_shot_ex({(11, 2): ["a"], (11, 3): ["b"]}, (11, 3))
    assert ids == ["b"] and exact is True


def test_empty_shot_ve_falls_back_and_is_not_exact():
    ids, exact = ve_ids_for_shot_ex({(11, 2): ["a"], (11, 3): []}, (11, 3))
    assert ids == ["a"] and exact is False


def test_no_scene_data_is_not_exact_and_empty():
    ids, exact = ve_ids_for_shot_ex({(11, 3): []}, (11, 3))
    assert ids == [] and exact is False


def test_legacy_wrapper_keeps_returning_ids_only():
    """기존 호출자 호환 — 리스트만 돌려준다."""
    assert ve_ids_for_shot({(1, 1): ["a"], (1, 2): []}, (1, 2)) == ["a"]


# ── 브리프 문구 ──────────────────────────────────────────────────────

def test_exact_assignment_keeps_exclusive_clause():
    p = _prompt(char_names=["정인우"], char_names_exact=True)
    assert "never anyone else" in p
    assert "scene-level" not in p


def test_scene_guess_drops_exclusive_clause():
    p = _prompt(char_names=["김지후(강수리영)"], char_names_exact=False)
    assert "never anyone else" not in p, "추측에 배타 조항이 붙으면 정답이 배제된다"
    assert "scene-level" in p
    assert "김지후(강수리영)" in p


def test_scene_guess_still_forbids_inventing_people():
    """배타 조항을 뺐다고 인물을 지어내도 된다는 뜻은 아니다."""
    p = _prompt(char_names=["김지후(강수리영)"], char_names_exact=False)
    assert "Never add a person the shot text does not show" in p


def test_scene_guess_defers_to_shot_text():
    p = _prompt(char_names=["김지후(강수리영)"], char_names_exact=False)
    assert "does not override the shot text" in p


def test_default_is_exact_so_existing_callers_are_unchanged():
    """`char_names_exact` 미전달 = 기존 동작. 바이트 동일이어야 한다."""
    assert _prompt(char_names=["정인우"]) == _prompt(
        char_names=["정인우"], char_names_exact=True)


def test_body_part_rule_survives_in_both_variants():
    for exact in (True, False):
        p = _prompt(char_names=["정인우"], char_names_exact=exact)
        assert "hand, arm, foot, back, silhouette" in p


def test_no_characters_is_unaffected_by_exact_flag():
    """인물이 없으면 두 플래그가 같은 결과를 낸다."""
    assert _prompt(char_names=[], char_names_exact=True) == _prompt(
        char_names=[], char_names_exact=False)


@pytest.mark.parametrize("names", [["A"], ["A", "B"], ["A", "B", "C"]])
def test_all_names_are_listed_in_scene_variant(names):
    p = _prompt(char_names=names, char_names_exact=False)
    for n in names:
        assert n in p
