"""Wave3 (e2e-review-fix-v1, 2026-05-21) — entity_extractor_v2 active-stem hygiene.

C8 W1a-2 가 entity_extractor_v2 의 turn2.md 닫힌 변형 목록을 추상화했으나,
turn2.md 는 active 파이프라인 code 가 로드하지 않는 dead stem 이었다 — 그 작업은
cosmetic. 실제 active leak 은 production loader 가 사용하는 system.md /
turn0_style.md 에 잔존했고, Wave3 가 v12 prompt-pack 에서 추상화했다.

본 test 는:
- G-active-loader : system / turn0_style (+ turn1·turn1_7_detail_batch·turn_entity_detail)
                    가 실제 production loader 경로임을 증명.
- G-dead          : turn2 / turn3 / turn4 가 dead stem 임을 증명 (C8 turn2 작업 = cosmetic).
- G-residue       : v12 system.md / turn0_style.md 에 닫힌 예시·문화권 leak 부재.
- G-intent        : 변형 허용/금지 구조 + turn0_style 필드 보존.
- G-boundary      : canonical VWR clause + metadata_json 계약 보존, double-brace 무도입.
- G-sibling       : v12 가 v11 대비 정확히 2 stem 만 수정.
- G-activation    : prompt_loader 가 v12 를 active 로 resolve.
- G-registry      : version_registry provenance v12 / 1.5.0 / 2026-05-21.

파일 read + 문자열 검사 + sha256 only — no live LLM, NO VLM.
residue needle 은 split-string 상수 (test 파일 self-hit 방지).
"""
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, get_effective_source

_REPO = Path(__file__).resolve().parents[3]
_PROMPTS = _REPO / "prompts" / "_base"
_BACKEND = _REPO / "backend"
_V3 = _BACKEND / "app" / "modules" / "pipeline" / "entity_extractor_v3.py"
_ENTITY_STEPS = _BACKEND / "app" / "core" / "steps" / "entity_steps.py"

# canonical VWR clause — test_area_7b_no_drift.py 와 동일한 glyph-exact 정의.
_CANONICAL_VWR = (
    "본 prompt 의 변신/빙의/원격접속 판단 기준은 visual_world_rules.rules[] 의 "
    "rule_type ∈ {possession, transformation, ghost, projection, superpower, "
    "body_deformation} 을 따른다. 작품에 해당 rule_type rule 이 정의되어 있지 "
    "않으면 일반 인물로 처리."
)

# v12 가 제거한 system.md 변형 닫힌 예시 needle (split-string — self-hit 방지).
_SYS_AGE_EX = "20대 " + "→ 60대"
_SYS_COMBAT_EX = "전투 준비" + " 상태"
_SYS_BOUND_EX = "결박된" + " 상태"
_SYS_SEVER_EX = "두 동강" + " 난 상태"
_SYS_MEMORY_EX = "기억 속" + " 모습"

# v12 가 제거한 turn0_style.md hardcoded 문화권/지역 예시 needle (split-string).
_TS_ERA_EX = "조선시대, " + "중세 유럽"
_TS_REGION_EX = "한국 서울, " + "미국 뉴욕"
_TS_GENRE_EX = "SF 액션, " + "사극"
_TS_BUILDING_EX = "조선시대 " + "한옥"
_TS_CLOTHING_EX = "한복, " + "중세 갑옷"


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()


# ──────────────── G-active-loader — system / turn0_style 가 active ────────────────


def test_g_active_loader_system_turn0_style():
    """G-active-loader: production code 가 system·turn0_style 를 포함한 5 stem 을
    entity_extractor_v2 pack 에서 실제 로드 — active stem 증명."""
    v3 = _V3.read_text(encoding="utf-8")
    steps = _ENTITY_STEPS.read_text(encoding="utf-8")
    assert '_MODULE = "entity_extractor_v2"' in v3, "_MODULE 바인딩 부재"
    assert "_load_prompt(" in v3, "_load_prompt 호출 부재"
    # active stem literal — entity_extractor_v3._load_prompt 인자로만 등장.
    for stem in ('"system"', '"turn0_style"', '"turn1"',
                 '"turn1_7_detail_batch"', '"turn_entity_detail"'):
        assert stem in v3, f"active stem 로드 인자 부재: {stem}"
    # entity_steps.py — system 직접 로드 (단일 라인 호출).
    assert 'load_prompt("entity_extractor_v2", "system")' in steps, \
        "entity_steps.py entity_extractor_v2 system 로드 부재"


# ──────────────── G-dead — turn2 / turn3 / turn4 는 dead stem ────────────────


def test_g_dead_turn2_turn3_turn4_not_loaded():
    """G-dead: turn2/turn3/turn4 는 entity_extractor_v2 를 쓰는 어떤 active code
    도 로드하지 않는 dead stem. C8 W1a-2 의 turn2.md 추상화가 active 파이프라인에
    효과 0 인 cosmetic 작업이었음을 회귀 가드."""
    v3 = _V3.read_text(encoding="utf-8")
    steps = _ENTITY_STEPS.read_text(encoding="utf-8")
    for dead in ('"turn2"', '"turn3"', '"turn4"'):
        assert dead not in v3, f"dead stem 이 entity_extractor_v3 에서 참조됨: {dead}"
        assert dead not in steps, f"dead stem 이 entity_steps 에서 참조됨: {dead}"


# ──────────────── G-residue — v12 추상화 leak 부재 ────────────────


def test_g_residue_system_transformation_examples():
    """G-residue: v12 system.md 변형 섹션에 닫힌 시나리오 예시 목록 부재."""
    sysmd = (_latest_dir("entity_extractor_v2") / "system.md").read_text(encoding="utf-8")
    assert _SYS_AGE_EX not in sysmd, "system.md 나이 변형 닫힌 example 잔존"
    assert _SYS_COMBAT_EX not in sysmd, "system.md 전투 준비 상태 example 잔존"
    assert _SYS_BOUND_EX not in sysmd, "system.md 결박 상태 example 잔존"
    assert _SYS_SEVER_EX not in sysmd, "system.md 신체 절단 상태 example 잔존"
    assert _SYS_MEMORY_EX not in sysmd, "system.md 기억 속 모습 example 잔존"


def test_g_residue_turn0_style_hardcoded_examples():
    """G-residue: v12 turn0_style.md 에 hardcoded 문화권/지역 예시 부재."""
    ts = (_latest_dir("entity_extractor_v2") / "turn0_style.md").read_text(encoding="utf-8")
    assert _TS_ERA_EX not in ts, "turn0_style.md era 닫힌 예시 잔존"
    assert _TS_REGION_EX not in ts, "turn0_style.md region 닫힌 예시 잔존"
    assert _TS_GENRE_EX not in ts, "turn0_style.md genre 닫힌 예시 잔존"
    assert _TS_BUILDING_EX not in ts, "turn0_style.md building_style 닫힌 예시 잔존"
    assert _TS_CLOTHING_EX not in ts, "turn0_style.md clothing_style 닫힌 예시 잔존"


# ──────────────── G-intent — 의도 보존 ────────────────


def test_g_intent_preserved():
    """G-intent: system.md 변형 허용/금지 구조 + turn0_style.md 8 필드명 보존."""
    sysmd = (_latest_dir("entity_extractor_v2") / "system.md").read_text(encoding="utf-8")
    assert "### 변형 — 매우 엄격하게 제한" in sysmd, "변형 섹션 헤더 손실"
    assert "허용:" in sysmd and "금지:" in sysmd, "변형 허용/금지 구조 손실"
    assert "참조 이미지가 별도로 필요" in sysmd, "변형 핵심 판단 기준 손실"
    ts = (_latest_dir("entity_extractor_v2") / "turn0_style.md").read_text(encoding="utf-8")
    for field in ("era:", "region:", "genre:", "building_style:", "clothing_style:",
                  "vehicle_style:", "color_tone:", "must_avoid:"):
        assert field in ts, f"turn0_style.md 필드 손실: {field}"


# ──────────────── G-boundary — target 밖 계약 보존 ────────────────


def test_g_boundary_vwr_clause_and_metadata_preserved():
    """G-boundary: canonical VWR clause + metadata_json 계약이 v12 에서 byte-보존,
    system.md double-brace 무도입."""
    sysmd = (_latest_dir("entity_extractor_v2") / "system.md").read_text(encoding="utf-8")
    ts = (_latest_dir("entity_extractor_v2") / "turn0_style.md").read_text(encoding="utf-8")
    assert _CANONICAL_VWR in sysmd, "system.md canonical VWR clause 손실"
    assert _CANONICAL_VWR in ts, "turn0_style.md canonical VWR clause 손실"
    assert "reference_required" in sysmd, "metadata_json reference_required 계약 손실"
    assert '"kind": "single_space"' in sysmd, "metadata_json single_space 예시 손실"
    assert "{{" not in sysmd and "}}" not in sysmd, "system.md double-brace 도입 금지"


# ──────────────── G-sibling — v12 가 정확히 2 stem 만 수정 ────────────────


def test_g_sibling_v12_modifies_exactly_two_stems():
    """G-sibling: v12 는 직전 dir(v11) 대비 정확히 system.md + turn0_style.md
    2 stem 만 수정, 나머지 stem 은 byte-identical (pack drift 0)."""
    vers = _list_module_versions("entity_extractor_v2")
    new_d = _PROMPTS / "entity_extractor_v2" / vers[0]
    old_d = _PROMPTS / "entity_extractor_v2" / vers[1]
    new_files = sorted(p.name for p in new_d.iterdir() if p.is_file())
    old_files = sorted(p.name for p in old_d.iterdir() if p.is_file())
    assert new_files == old_files, f"v12 pack stem drift: {new_files} vs {old_files}"
    modified = sorted(
        name for name in new_files
        if _sha256(new_d / name) != _sha256(old_d / name)
    )
    assert modified == ["system.md", "turn0_style.md"], \
        f"v12 수정 stem 이 예상(system.md+turn0_style.md)과 다름: {modified}"


# ──────────────── G-activation — loader 가 v12 active resolve ────────────────


def test_g_activation_v12_latest():
    """G-activation: prompt_loader 가 entity_extractor_v2 v12 를 active 로 resolve."""
    src = get_effective_source("entity_extractor_v2", "system")
    assert src["module_pack"]["latest"].startswith("12."), "v12 미활성"
    assert "/12." in src["candidates"]["file"]["path"], "system.md v12 경로 아님"


# ──────────────── G-registry — version_registry provenance ────────────────


def test_g_registry_v12_provenance():
    """G-registry: version_registry entity_extractor → v12 / 1.5.0 / 2026-05-21."""
    assert _MODULE_INFO["entity_extractor"]["prompt_dependency"] == "entity_extractor_v2/v12"
    assert _MODULE_INFO["entity_extractor"]["updated_at"] == "2026-05-21"
    assert MODULE_VERSIONS["entity_extractor"] == "1.5.0"
