"""Carry-P021 — Scene Consistency Prompt Hygiene v1 integrated test.

7 Gate verify (closed-world exact phrase membership only):
- G1: Active v8 source-path proof (get_effective_source dict + sibling schema.json existence, separation per N-A6).
- G2: v8 file count 2 (system.md + schema.json).
- G3: STRICT_RESIDUES marker-wrapped 10 strict R-id absence in v8 system.md (single constant per Codex N-6).
- G4: ABSTRACT_PRESENCE_CLAUSES tuple presence in v8 system.md (R6 lead + R6 second + R1-R5 process clause, exact fixed-string only per Codex W3 guard).
- G5: Schema sha256 byte-identical canary v7 == v8.
- G6: version_registry 3 logical edits sync (Site 1/2/3) + STEP_MANIFEST schema_version=3 preserve + SCENE_CONSISTENCY_SCHEMA_VERSION=3 preserve (Area #4 boundary canary).
- G7: v7 historical canary (dir + 2 file existence + schema sha256 length 64 computability smoke per Codex W3 guard).

NO VLM + no live LLM + no regex semantic + no standalone token global ban.
"""

import hashlib
from pathlib import Path

from app.core.step_manifest import STEP_MANIFEST
from app.core.steps.scene_consistency_step import SCENE_CONSISTENCY_SCHEMA_VERSION
from app.core.version_registry import MODULE_VERSIONS, _MODULE_INFO
from app.modules.prompt_loader import get_effective_source


REPO_ROOT = Path(__file__).resolve().parents[3]
SC_V7_DIR = REPO_ROOT / "prompts" / "_base" / "scene_consistency" / "7.202605181210"


# <!-- HISTORICAL AUDIT TRAIL — BEGIN -->
# STRICT_RESIDUES single constant per Codex outline N-6 정합.
# 10 strict ids: R1-R6 (system.md line 13/14/18/22/58/61) + JS1/JS2a/JS2b/JS3 (system.md line 89-110 JSON example sub-anchors).
# Exact quoted wording = spec §2.1.1 marker block 안 인용; 본 test file = closed-world test constants reference only.
# Marker block 외부 (test name / docstring / 다른 변수) exact residue 재방출 0 (Codex N-A3 doctrine + post-Carry-D7d-1 fix-up 1 강화 정합).
STRICT_RESIDUES = (
    # R1 — system.md:13 (v7), hard-prime rest-state example
    'a young person resting on a sofa, curled on the left side, one arm tucked under the cheek, eyes closed, a light blanket draped over the shoulder',
    # R2 — system.md:14 (v7), hard-prime localized bandage example
    'a figure seated against a wall, head tilted back, eyes closed, a small bandage wrapped around the left wrist',
    # R3 — system.md:18 (v7), hard-prime shattered-window example
    'the left pane of the window is shattered with jagged glass edges remaining in the frame',
    # R4 — system.md:22 (v7), hard-prime bedsheet example
    'a crumpled white bedsheet hanging off the bed edge, touching the floor',
    # R5 — system.md:58 (v7), hard-prime concrete posture phrase (Codex V3 OQ-2 strict: quoted English phrase only)
    'lying face-down on the wooden floor, left arm extended forward, right knee slightly bent',
    # R6 — system.md:61 (v7), demographic default instruction (full-phrase, Carry-A100 lineage)
    '8. **인종/국적 명기**: 인물 묘사 시 인종/국적을 반드시 포함',
    # JS1 — system.md:95 (v7), JSON example description
    'an East Asian person lying motionless on the left side, left arm bent near the face, eyes closed',
    # JS2a — system.md:99 (v7), JSON example source_facts
    "S## action: 'A figure lies motionless on the floor.'",
    # JS2b — system.md:100 (v7), JSON example source_facts heading
    'scene heading: NIGHT, small interior room',
    # JS3 — system.md:103 (v7), JSON example visual_inferences
    'left-side recumbent pose (text only specifies motionless, not orientation)',
)
# <!-- HISTORICAL AUDIT TRAIL — END -->


# ABSTRACT_PRESENCE_CLAUSES per spec §6.2 G4 + Codex V3 OQ-3 default pin Carry-A100 lineage 정합.
# Exact fixed-string clauses only, no semantic regex (Codex W3 guard 정합).
ABSTRACT_PRESENCE_CLAUSES = (
    # R6 abstraction Korean lead clause
    '8. **시각적 인구학 단서 반영**:',
    # R6 abstraction Korean second clause (Carry-A100 lineage default-coerce 강제 X)
    '명시되지 않은 인물에 default 인구학 표기를 강제하지 않는다',
    # R1-R5 abstraction Korean clause (process wording)
    '원문에 명시된 inactive/restful pose는 구체 자세',
)


def _v8_dir() -> Path:
    """v8 active dir resolution via get_effective_source source-path proof (separation per N-A6)."""
    src = get_effective_source("scene_consistency", "system", db=None)
    file_cand = src["candidates"]["file"]
    assert file_cand is not None, "no file candidate for scene_consistency/system"
    fpath = Path(file_cand["path"]).resolve()
    return fpath.parent


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


def test_gate_1_active_v8_source_path_proof():
    """G1: Active v8 source-path proof via get_effective_source dict + sibling schema.json existence (separation per N-A6 + Codex amend 1 IMPORTANT 1 fix)."""
    src = get_effective_source("scene_consistency", "system", db=None)
    file_cand = src["candidates"]["file"]
    assert file_cand is not None
    # System prompt source-path proof
    assert str(file_cand["version"]).startswith("8."), file_cand["version"]
    assert "/scene_consistency/8." in str(file_cand["path"]), file_cand["path"]
    # Schema proof = system path sibling schema.json existence (separation)
    # get_effective_source('scene_consistency', 'schema') 사용 X (.md stem only, schema.md 찾음 — schema.json source-path proof X)
    # load_prompt / load_schema = content-load smoke only, source-path proof X
    # get_active_version = module-level active-version, schema stem source-path proof X
    system_path = Path(file_cand["path"])
    schema_path = system_path.parent / "schema.json"
    assert schema_path.is_file(), f"schema sibling existence FAIL: {schema_path}"
    assert "/scene_consistency/8." in str(schema_path), f"schema path FAIL: {schema_path}"


def test_gate_2_v8_file_count_2():
    """G2: v8 dir file count = 2 (system.md + schema.json)."""
    v8_dir = _v8_dir()
    files = sorted(p.name for p in v8_dir.iterdir() if p.is_file())
    assert files == ["schema.json", "system.md"], f"unexpected v8 file list: {files}"


def test_gate_3_strict_residues_absent_in_v8_system_md():
    """G3: STRICT_RESIDUES marker-wrapped 10 strict R-id full-phrase membership hit 0 in v8 system.md."""
    v8_system = _v8_dir() / "system.md"
    text = v8_system.read_text(encoding="utf-8")
    for residue in STRICT_RESIDUES:
        assert residue not in text, f"strict residue still present in v8 system.md: {residue!r}"


def test_gate_4_abstract_wording_presence_in_v8_system_md():
    """G4: ABSTRACT_PRESENCE_CLAUSES tuple presence in v8 system.md (exact fixed-string, no semantic regex per Codex W3 guard)."""
    v8_system = _v8_dir() / "system.md"
    text = v8_system.read_text(encoding="utf-8")
    for clause in ABSTRACT_PRESENCE_CLAUSES:
        assert clause in text, f"required abstract wording missing in v8 system.md: {clause!r}"


def test_gate_5_schema_sha256_byte_identical_from_v7():
    """G5: schema.json sha256 byte-identical canary v7 == v8 (Carry-A099 G3 + Carry-A100 G5 lineage)."""
    v7_schema = SC_V7_DIR / "schema.json"
    v8_schema = _v8_dir() / "schema.json"
    assert _sha256(v7_schema) == _sha256(v8_schema)


def test_gate_6_version_registry_3_logical_edits_and_step_manifest_preserve():
    """G6: version_registry 3 logical edits sync + STEP_MANIFEST schema_version=3 preserve + SCENE_CONSISTENCY_SCHEMA_VERSION=3 preserve (Codex V3 OQ-5 + Area #4 boundary canary 정합)."""
    # Site 1 MODULE_VERSIONS bump
    assert MODULE_VERSIONS["scene_consistency"] == "2.6.0"
    # Site 2 prompt_dependency sync
    assert _MODULE_INFO["scene_consistency"]["prompt_dependency"] == "scene_consistency/v8"
    # Site 3 updated_at sync (Codex outline N-10 흡수 — Site 3 updated_at verify 추가)
    assert _MODULE_INFO["scene_consistency"]["updated_at"] == "2026-05-20"
    # STEP_MANIFEST schema_version preserve (Codex V3 OQ-5 default: verify-only)
    assert STEP_MANIFEST["scene_consistency"]["schema_version"] == 3
    # SCENE_CONSISTENCY_SCHEMA_VERSION constant preserve (Area #4 boundary)
    assert SCENE_CONSISTENCY_SCHEMA_VERSION == 3


def test_gate_7_v7_historical_canary():
    """G7: v7 historical canary — dir + 2 file existence + schema sha256 length 64 computability smoke (Codex amend 1 MINOR 2 fix + W3 guard 정합 — no pinned hash literal in test, optional expected hash pin at execution time)."""
    assert SC_V7_DIR.is_dir(), f"v7 historical dir missing: {SC_V7_DIR}"
    v7_system = SC_V7_DIR / "system.md"
    v7_schema = SC_V7_DIR / "schema.json"
    assert v7_system.is_file(), f"v7 historical system.md missing"
    assert v7_schema.is_file(), f"v7 historical schema.json missing"
    # sha256 length 64 computability smoke (no pinned hash literal in test source — W3 execution 시 optional expected hash pin 추가 가능)
    v7_schema_sha256 = _sha256(v7_schema)
    assert len(v7_schema_sha256) == 64
