"""Carry-A100 — VWR system.md t2i_context demonym + rules_schema.json:40 description abstraction integrated test.

7 Gate verify (closed-world exact phrase membership only):
- Gate 1 (G1): v9 active source-path proof via get_effective_source + get_active_version + schema-specific path existence.
- Gate 2 (G2): v9 dir file count == 2 (system.md + rules_schema.json).
- Gate 3 (G3): R1-R5 strict residue absence (closed-world full-phrase membership, marker-wrapped test constants).
- Gate 4 (G4): new abstract wording presence (system.md line 102 + rules_schema.json:40 description, fixed-string).
- Gate 5 (G5): carrier shape preserve (3-point: t2i_context.type == 'string' + 't2i_context' in required + additionalProperties == False).
- Gate 6 (G6): STEP_MANIFEST['visual_world_rules']['schema_version'] == 1 preserve.
- Gate 7 (G7): director_notes section preserve canary (v9 line 57-84 ≡ v8 line 57-84 byte-equal substring, Carry-A099 boundary).

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

import json
from pathlib import Path

from app.core.step_manifest import STEP_MANIFEST
from app.modules.prompt_loader import get_active_version, get_effective_source


REPO_ROOT = Path(__file__).resolve().parents[3]
VWR_V8_DIR = REPO_ROOT / "prompts/_base/visual_world_rules/8.202605191102"


# HISTORICAL AUDIT TRAIL — BEGIN (5-item strict residue catalog; literal phrases below are
# historical residue exact strings for closed-world test-constant use only, not live prompt
# content per Codex 2nd narrow re-review guard 3).
R1_R5_STRICT = [
    "<region-derived demonym>",
    'All human characters are <region-derived demonym> unless stated otherwise.',
    "인물의 기본 국적/인종",
    "시나리오의 region 에서 도출된 demonym",
    "기본 국적/인종 + 색감/조명/질감 톤 + 장르 무드",
]
# HISTORICAL AUDIT TRAIL — END

NEW_WORDING_SYSTEM = "시나리오 또는 인물별 outlook/character evidence에 명시된 인구학 단서의 반영 원칙"
NEW_WORDING_SCHEMA = "인물별 시각적 인구학 단서가 명시된 경우의 반영 원칙"


def _v9_dir() -> Path:
    src = get_effective_source("visual_world_rules", "system", db=None)
    file_cand = src.get("candidates", {}).get("file")
    assert file_cand is not None, "no file candidate for visual_world_rules/system"
    fpath = Path(file_cand["path"]).resolve()
    return fpath.parent


def test_gate_1_v9_active_source_path_proof():
    """G1: v9 active prompt-pack source-path proof via get_effective_source + get_active_version."""
    src = get_effective_source("visual_world_rules", "system", db=None)
    file_cand = src.get("candidates", {}).get("file")
    assert file_cand is not None
    assert str(file_cand["version"]).startswith("9."), file_cand["version"]
    assert "/visual_world_rules/9." in str(file_cand["path"]), file_cand["path"]

    module_ver = get_active_version("visual_world_rules", "rules_schema", db=None)
    assert module_ver and module_ver.startswith("9."), module_ver

    v9 = _v9_dir()
    schema_file = v9 / "rules_schema.json"
    assert schema_file.is_file(), f"schema-specific file existence FAIL: {schema_file}"
    assert "/visual_world_rules/9." in str(schema_file), f"schema-specific path FAIL: {schema_file}"


def test_gate_2_v9_file_count():
    """G2: v9 dir 안 file count == 2."""
    v9 = _v9_dir()
    files = sorted([p.name for p in v9.iterdir() if p.is_file()])
    assert files == ["rules_schema.json", "system.md"], files


def test_gate_3_r1_r5_residue_absent_in_v9():
    """G3: 5 strict R-id full-phrase membership hit 0 in v9 system.md (R1-R4) + rules_schema.json (R5)."""
    v9 = _v9_dir()
    sys_md = (v9 / "system.md").read_text(encoding="utf-8")
    sch_json = (v9 / "rules_schema.json").read_text(encoding="utf-8")
    for idx, residue in enumerate(R1_R5_STRICT[:4], start=1):
        assert residue not in sys_md, f"R{idx} residue present in v9 system.md"
    assert R1_R5_STRICT[4] not in sch_json, "R5 residue present in v9 rules_schema.json"


def test_gate_4_new_abstract_wording_presence():
    """G4: new abstract wording present in v9 system.md + rules_schema.json description."""
    v9 = _v9_dir()
    sys_md = (v9 / "system.md").read_text(encoding="utf-8")
    sch_json = (v9 / "rules_schema.json").read_text(encoding="utf-8")
    assert NEW_WORDING_SYSTEM in sys_md, "new system.md wording absent"
    assert NEW_WORDING_SCHEMA in sch_json, "new rules_schema.json description absent"


def test_gate_5_carrier_shape_preserve():
    """G5: rules_schema.json carrier shape preserve (3-point: type=string + required + additionalProperties=false)."""
    v9 = _v9_dir()
    schema = json.loads((v9 / "rules_schema.json").read_text(encoding="utf-8"))
    t2i_ctx = schema["properties"]["t2i_context"]
    assert t2i_ctx["type"] == "string", f"t2i_context.type drift: {t2i_ctx['type']}"
    assert "t2i_context" in schema.get("required", []), f"t2i_context missing from required: {schema.get('required')}"
    assert schema.get("additionalProperties") is False, f"additionalProperties drift: {schema.get('additionalProperties')}"


def test_gate_6_step_manifest_schema_version_preserve():
    """G6: STEP_MANIFEST['visual_world_rules']['schema_version'] == 1 preserve."""
    assert STEP_MANIFEST["visual_world_rules"]["schema_version"] == 1


def test_gate_7_director_notes_section_preserve_canary():
    """G7: v9 line 57-84 director_notes section content ≡ v8 line 57-84 (byte-equal substring, Carry-A099 boundary canary).

    Carry-A099 W1 anchor preservation: v9 안 director_notes section content 는 v8 안 same range 와 byte-equal.
    """
    v9 = _v9_dir()
    v9_lines = (v9 / "system.md").read_text(encoding="utf-8").splitlines()
    v8_lines = (VWR_V8_DIR / "system.md").read_text(encoding="utf-8").splitlines()
    v9_section = "\n".join(v9_lines[56:84])  # line 57-84 (0-indexed [56:84])
    v8_section = "\n".join(v8_lines[56:84])
    assert v9_section == v8_section, "director_notes section drift v8 → v9 (Carry-A099 boundary 위반)"
