"""C5 — Action segment / still-moment prompt hygiene regression tests.

fix-critical-1 Tier β #8 (roadmap §5.9). 옵션 B narrow — 닫힌 lexicon → generic
semantic principle 추상화. 신규 action_segment[] schema 0.

W1 (shot_extract v13) / W2 (shot_validator v6) / W3 (scene_detail v32) prompt-pack 의
시간 연결어 / locomotion / active-contact / visible-human-action / dynamic-verb 닫힌
목록 추상화 + version sync 를 검증한다.

파일 read + 문자열 검사 + sha256 only — no live LLM, NO VLM.
닫힌-form residue needle 은 split-string 상수 (test 파일 self-hit 방지).

Gate map (spec §7 / plan §7):
- W1 G1-G3 + G_reg_W1 + G_loader_W1 — shot_extract v13 (본 wave)
- W2 G4-G8 — shot_validator v6 (W2 append)
- W3 G9-G10 — scene_detail v32 (W3 append)
"""
from __future__ import annotations

import hashlib
from pathlib import Path

from app.core.version_registry import MODULE_VERSIONS, _MODULE_INFO
from app.modules.prompt_loader import (
    _list_module_versions,
    _resolve_stem_in_pack,
    load_prompt,
    load_schema,
)

_PROMPTS = Path(__file__).resolve().parents[3] / "prompts" / "_base"


def _latest_dir(module: str) -> Path:
    """module 의 numeric-latest prompt-pack 디렉토리."""
    return _PROMPTS / module / _list_module_versions(module)[0]


def _sha256(p: Path) -> str:
    return hashlib.sha256(p.read_bytes()).hexdigest()


# ───────────────────────── W1 — shot_extract v13 ─────────────────────────


def test_w1_g1_shot_extract_user_time_connective_list_removed():
    """G1: v13 user.md 시간 연결어 닫힌 목록 absent + still-moment 원칙 보존."""
    d = _latest_dir("shot_extract")
    user = (d / "user.md").read_text(encoding="utf-8")
    assert ('"~하자" / ' + '"~하면서"') not in user, "A064 한국어 시간 연결어 목록 잔존(user)"
    assert ('"and then", ' + '"while ~ing"') not in user, "A064 영어 시간 연결어 목록 잔존(user)"
    # E2E13 fix①(v17): 표제 '단일 정지 순간' → '단일 찰나' (정지 어투가
    # 하류 정적 연출을 재주입하던 실측 대응). 단일 순간 원칙 자체는 보존.
    assert "단일 찰나" in user, "single-moment 원칙(단일 찰나) 손실(user)"
    assert "하나의 동작/상태만" in user, "single-moment 원칙 손실(user)"


def test_w1_g2_shot_extract_system_time_connective_list_removed():
    """G2: v13 system.md 시간 연결어 닫힌 목록 absent + header/원칙 보존."""
    d = _latest_dir("shot_extract")
    system = (d / "system.md").read_text(encoding="utf-8")
    assert ('"~하자", ' + '"~하면서"') not in system, "A068 한국어 시간 연결어 목록 잔존(system)"
    assert ('"and then", ' + '"while ~ing"') not in system, "A068 영어 시간 연결어 목록 잔존(system)"
    assert ('"~하려는 ' + '찰나"') not in system, "A068 찰나형 시간 연결어 잔존(system)"
    assert "## 시간 연결어 절대 금지" in system, "section header 손실(system)"
    assert "단일 동사/상태" in system, "still-moment 원칙(단일 동사/상태) 손실(system)"
    assert "두 개 이상의 순간이 섞인 것" in system, "intent(두 순간 혼합 금지) 손실(system)"


def test_w1_g3_shot_extract_v13_stems_system_user_only():
    """G3: v13 pack = system.md + user.md only — shot_schema.json 미통합 (W1 OQ blocker 옵션 1)."""
    d = _latest_dir("shot_extract")
    for stem in ("system.md", "user.md"):
        assert (d / stem).is_file(), f"v13 prompt-pack stem 누락: {stem}"
    assert not (d / "shot_schema.json").is_file(), (
        "v13 에 shot_schema.json 통합 — W1 OQ blocker 옵션 1 위반 "
        "(schema 는 v10 per-stem fallback 유지)"
    )


def test_w1_g_reg_shot_extractor_version_synced():
    """G_reg_W1: version_registry shot_extractor 2.4.0 + prompt_dependency v15 (Wave1 sync; 이전 C8 W2a 2.3.0/v14)."""
    assert MODULE_VERSIONS["shot_extractor"] == "2.6.0", (  # E2E13 fix① v17
        f"{MODULE_VERSIONS['shot_extractor']!r} != '2.6.0' (E2E13 v17 bump)"
    )
    pdep = _MODULE_INFO["shot_extractor"]["prompt_dependency"]
    assert pdep == "shot_extract/v17", f"{pdep!r} != 'shot_extract/v17'"


def test_w1_g_loader_shot_extract_v13_resolves():
    """G_loader_W1: system/user v15 resolve + shot_schema v10 per-stem fallback (Wave1 sync; 이전 C8 W2a v14)."""
    versions = _list_module_versions("shot_extract")
    assert versions, "shot_extract prompt directory 비어 있음"
    # E2E13 fix①: v17 (키샷 가치 관통) latest-activation bump
    assert versions[0].split(".")[0] == "17", (
        f"latest shot_extract major={versions[0]!r} != '17'"
    )
    _, sys_ver, _ = _resolve_stem_in_pack("shot_extract", "system", ".md")
    _, usr_ver, _ = _resolve_stem_in_pack("shot_extract", "user", ".md")
    _, schema_ver, _ = _resolve_stem_in_pack("shot_extract", "shot_schema", ".json")
    assert sys_ver is not None and sys_ver.split(".")[0] == "17", (
        f"system.md resolve v{sys_ver} != v17"
    )
    assert usr_ver is not None and usr_ver.split(".")[0] == "17", (
        f"user.md resolve v{usr_ver} != v17"
    )
    assert schema_ver is not None and schema_ver.split(".")[0] == "10", (
        f"shot_schema resolve v{schema_ver} != v10 — per-stem fallback 기대 (W1 OQ blocker)"
    )
    assert load_prompt("shot_extract", "system"), "system.md load 실패"
    assert load_prompt("shot_extract", "user"), "user.md load 실패"
    assert load_schema("shot_extract", "shot_schema"), "shot_schema load 실패"


# ───────────────────────── W2 — shot_validator v6 ─────────────────────────

# C5 W2 직전 active prompt-pack — validator_schema.json byte-identical 기준 (frozen dir).
_SHOT_VALIDATOR_V5_DIR = "5.202605081700"


def test_w2_g4_shot_validator_time_connective_list_removed():
    """G4: v6 system.md 시간 연결어 닫힌 목록 absent + header/결과상태 섹션 보존."""
    sysmd = (_latest_dir("shot_validator") / "system.md").read_text(encoding="utf-8")
    assert ('"~하자", ' + '"~하면서"') not in sysmd, "A082 시간 연결어 닫힌 목록 잔존"
    assert ('"~하려는 ' + '찰나"') not in sysmd, "A082 찰나형 시간 연결어 잔존"
    assert "## 시간 연결어 절대 금지 (순차 동작 연결)" in sysmd, "section header 손실"
    assert "## 결과 상태 묘사는 허용" in sysmd, "결과 상태 허용 섹션 (판단기준 contract) 손실"


def test_w2_g5_shot_validator_locomotion_lexicon_removed():
    """G5: v6 system.md locomotion 카테고리 lexicon absent + motion 원칙 보존."""
    sysmd = (_latest_dir("shot_validator") / "system.md").read_text(encoding="utf-8")
    assert ("running / sprinting" + " / walking") not in sysmd, "A082 locomotion 영문 lexicon 잔존"
    assert ("걷기·" + "달리기") not in sysmd, "locomotion 한국어 mini-list 재도입"
    assert "## 동적 동사 freeze 규칙" in sysmd, "동적 동사 freeze 섹션 header 손실"
    assert "이동·추진 동작" in sysmd, "이동·추진 동작 generic 원칙 손실"


def test_w2_g6_shot_validator_active_contact_lexicon_removed():
    """G6: v6 system.md active contact lexical bullet absent + freeze contract 보존."""
    sysmd = (_latest_dir("shot_validator") / "system.md").read_text(encoding="utf-8")
    assert ("stabbing, slashing" + ", piercing") not in sysmd, "A082 active contact 찌르기 영문 lexicon 잔존"
    assert ("punching, striking" + ", slamming") not in sysmd, "A082 active contact 타격 영문 lexicon 잔존"
    assert ("찌름, " + "베기, 칼날") not in sysmd, "A082 active contact 한국어 lexicon 잔존"
    assert ("제압, 목" + "조름") not in sysmd, "A082 active contact 한국어 lexicon 잔존"
    assert ("찌르기·" + "베기·타격") not in sysmd, "active contact 한국어 mini-list 재도입"
    assert "Active contact freeze rule" in sysmd, "active contact freeze rule 섹션 손실"
    assert "가이드일 뿐" in sysmd, "active contact 카테고리 disclaimer 손실"
    assert "힘의 방향" in sysmd, "active contact 시각 단서 contract 손실"


def test_w2_g7_shot_validator_visible_human_action_lexicon_removed():
    """G7: v6 system.md visible-human-action 닫힌 lexicon absent + fail-fast contract 보존."""
    sysmd = (_latest_dir("shot_validator") / "system.md").read_text(encoding="utf-8")
    assert ("얼굴, 손, " + "다리, 팔") not in sysmd, "A083 visible-human-action 신체부위 한국어 lexicon 잔존"
    assert ("face, hand, leg" + ", arm") not in sysmd, "A083 visible-human-action 신체부위 영문 lexicon 잔존"
    assert ("잡기, 보기, " + "말하기") not in sysmd, "A083 visible-human-action 동작 한국어 lexicon 잔존"
    assert ("hold, look, talk" + ", embrace") not in sysmd, "A083 visible-human-action 동작 영문 lexicon 잔존"
    assert ("폭력·친밀·" + "대화·응시") not in sysmd, "visible-human-action 한국어 mini-list 재도입"
    assert "visible-human-action" in sysmd, "visible-human-action 개념명 손실"
    assert "fail-fast" in sysmd, "characters/character_ids fail-fast contract 손실"


def test_w2_g8_shot_validator_schema_and_version_synced():
    """G8: validator_schema.json == v5 sha256 + version_registry sync + loader resolve.

    ★2026-08-25 — 판 번호를 **못박던 것을 자기 갱신으로** 바꿨다. 원래는
    `"1.5.0"` · `"shot_validator/v6"` · `major == "6"` 을 그대로 비교해서,
    프롬프트를 정당하게 올릴 때마다 이 시험이 빨개졌다. 실제로 v7(2026-08-07)
    이 나간 뒤 이 시험은 계속 실패 상태였고, 그래서 아무도 안 보는 빨간불이
    됐다 — registry 가 v6 에 멈춰 있다는 **진짜 신호**까지 같이 묻혔다.
    지키려던 것은 「registry 가 최신 팩과 어긋나지 않는 것」이지 특정 번호가
    아니다. 같은 저장소의 `test_scene_detail_version_registry_aligned` 와 같은
    방식으로 맞춘다.

    변하지 않는 검사(스키마 바이트 동일·로더 해석)는 그대로 둔다.
    """
    d = _latest_dir("shot_validator")
    src = _PROMPTS / "shot_validator" / _SHOT_VALIDATOR_V5_DIR / "validator_schema.json"
    assert _sha256(d / "validator_schema.json") == _sha256(src), (
        "validator_schema.json byte-identical 위반 — 최신 팩 != v5"
    )
    versions = _list_module_versions("shot_validator")
    latest_major = versions[0].split(".")[0]

    pdep = _MODULE_INFO["shot_validator"]["prompt_dependency"]
    assert pdep == f"shot_validator/v{latest_major}", (
        f"_MODULE_INFO['shot_validator']['prompt_dependency']={pdep!r} != "
        f"최신 v{latest_major} — registry 가 stale"
    )
    mv = MODULE_VERSIONS["shot_validator"]
    assert f".{latest_major}." in mv, (
        f"MODULE_VERSIONS['shot_validator']={mv!r} 의 minor 가 프롬프트 "
        f"v{latest_major} 와 정렬 안 됨 — version 동기화 누락"
    )
    assert load_prompt("shot_validator", "system"), "system.md load 실패"
    assert load_schema("shot_validator", "validator_schema"), "validator_schema load 실패"


# ───────────────────────── W3 — scene_detail v32 ─────────────────────────

# C5 W3 직전 active prompt-pack — detail_schema.json byte-identical 기준 (frozen dir).
_SCENE_DETAIL_V31_DIR = "31.202605200805"
# C5 W3 산출물 prompt-pack — FINDING 5 v33 bump 후 _latest_dir 가 v33 로 drift 하므로
# C5 regression 은 C5 의 실제 산출물 v32 에 고정 (frozen dir).
_SCENE_DETAIL_V32_DIR = "32.202605201133"


def test_w3_g9_scene_detail_dynamic_verb_list_removed():
    """G9: v32 system.md dynamic verb 닫힌 목록 absent + motion 원칙/예시 보존."""
    sysmd = (_latest_dir("scene_detail") / "system.md").read_text(encoding="utf-8")
    assert ("`running`, " + "`riding`") not in sysmd, "A042 dynamic verb backtick 목록 잔존 (:543)"
    assert ("running / riding " + "/ walking") not in sysmd, "A042 dynamic verb slash 목록 잔존 (:373)"
    assert ("걷기·달리기·" + "탈것") not in sysmd, "A042 이동 동작 한국어 mini-list 재도입"
    assert "motion direction" in sysmd, "motion direction 원칙 손실"
    assert "mid-stride" in sysmd, "mid-action freeze 예시 (canary) 손실"
    assert "이동·추진 동작" in sysmd, "이동·추진 동작 generic 원칙 손실"


def test_w3_g10_scene_detail_v32_schema_and_version_synced():
    """G10: C5 W3 산출물 v32 detail_schema.json == v31 sha256 + SCHEMA_VERSION 불변.

    FINDING 5 / e2e-bughunt-v1 W3 (2026-05-21): scene_detail v32 → v33 bump 으로
    `_latest_dir` 가 v33 로 이동 + v33 는 owned_object_usage description 을 수정
    (byte 변경). C5 의 "prompt-only, schema byte-identical" 주장은 C5 의 실제
    산출물 v32 에 대한 것이므로 본 gate 를 v32 frozen dir 에 고정한다. 글로벌
    4-point version cascade 정합 (SCENE_DETAIL_PROMPT_VERSION / MODULE_VERSIONS /
    _MODULE_INFO / loader latest) 은 test_scene_detail_continuity_alignment /
    test_scene_detail_spatial_alignment / test_prompt_versions 가 owns —
    본 gate 의 중복 latest-tracking assert 는 제거.
    """
    v32_dir = _PROMPTS / "scene_detail" / _SCENE_DETAIL_V32_DIR
    assert v32_dir.is_dir(), f"C5 v32 prompt-pack dir 부재: {v32_dir}"
    src = _PROMPTS / "scene_detail" / _SCENE_DETAIL_V31_DIR / "detail_schema.json"
    assert _sha256(v32_dir / "detail_schema.json") == _sha256(src), (
        "detail_schema.json byte-identical 위반 — v32 != v31 (C5 는 prompt-only)"
    )
    from app.core.steps.detail_steps import SCENE_DETAIL_SCHEMA_VERSION
    # reference-necessity Phase 2 (2026-05-23): render_prompt_card overlay → 12→13.
    assert SCENE_DETAIL_SCHEMA_VERSION == 13, (
        f"SCENE_DETAIL_SCHEMA_VERSION={SCENE_DETAIL_SCHEMA_VERSION!r} != 13 (reference-necessity Phase 2 bump)"
    )
    assert load_prompt("scene_detail", "system"), "system.md load 실패"
    assert load_schema("scene_detail", "detail_schema"), "detail_schema load 실패"
