"""E2E13 Codex HIGH-3(+재리뷰 HIGH-2) — step-local config hash 계약.

shot_extract/selection/staging 은 latest-dir(+DB active row) 프롬프트가
실질 계약 — project_config-only fallback hash 는 팩 교체·내용 변경 후에도
완료 CP 를 silent reuse 한다. step-local _config_hash 가 **실제 로드되는
effective prompt/schema 의 내용**에 민감한지 결정론으로 잠근다:
(a) 같은 dir 안의 내용 변경 (b) DB active winner 변경 — 둘 다
load_prompt/load_schema 경유이므로 로더 반환 내용 변화로 검증한다.
"""
from __future__ import annotations

import pytest

from app.core.steps.beat_shot_steps import ShotExtractStep
from app.core.steps.shot_selection_step import ShotSelectionStep
from app.core.steps.shot_staging_step import ShotStagingStep

_CASES = [
    (ShotExtractStep, "shot_extract", "system"),
    (ShotSelectionStep, "shot_selection", "system"),
    (ShotStagingStep, "shot_staging", "system"),
]


def _bare(cls):
    step = cls.__new__(cls)  # __init__ 우회 — hash 는 config/로더만 소비
    step.project_config = {}
    step.db = None
    return step


@pytest.mark.parametrize("cls,module,stem", _CASES)
def test_config_hash_folds_effective_prompt_content(cls, module, stem,
                                                    monkeypatch):
    """로더가 돌려주는 내용이 바뀌면(같은 dir 편집이든 DB winner 변경이든)
    hash 도 바뀐다 — dir 이름만 접던 공백(재리뷰 HIGH-2) 회귀 잠금."""
    import app.modules.prompt_loader as pl

    step = _bare(cls)
    h1 = step._config_hash()

    real = pl.load_prompt

    def fake(mod, name, *a, **kw):
        text = real(mod, name, *a, **kw)
        if mod == module and name == stem:
            return text + "\nCHANGED-BY-TEST"
        return text

    monkeypatch.setattr(pl, "load_prompt", fake)
    h2 = step._config_hash()
    assert h1 != h2, f"{module}: effective prompt 내용 변경이 hash 미반영"


@pytest.mark.parametrize(
    "cls,module,schema_stem",
    [
        (ShotExtractStep, "shot_extract", "shot_schema"),
        (ShotSelectionStep, "shot_selection", "selection_schema"),
        (ShotStagingStep, "shot_staging", "schema"),
    ],
)
def test_config_hash_folds_schema_content(cls, module, schema_stem,
                                          monkeypatch):
    import app.modules.prompt_loader as pl

    step = _bare(cls)
    h1 = step._config_hash()

    real = pl.load_schema

    def fake(mod, name, *a, **kw):
        data = real(mod, name, *a, **kw)
        if mod == module and name == schema_stem:
            return {**data, "x_changed_by_test": True}
        return data

    monkeypatch.setattr(pl, "load_schema", fake)
    h2 = step._config_hash()
    assert h1 != h2, f"{module}: schema 내용 변경이 hash 미반영"


@pytest.mark.parametrize("cls,module,stem", _CASES)
def test_config_hash_stable_for_same_inputs(cls, module, stem):
    step = _bare(cls)
    assert step._config_hash() == step._config_hash()


def test_staging_pack_has_no_paused_pose_contract():
    """재리뷰 HIGH-1: 활성 staging 팩(system+schema)이 정적 'paused' 자세를
    재주입하지 않는다 — LLM 실입력 스템 전체 잠금."""
    import json
    from pathlib import Path

    from app.modules.prompt_loader import _list_module_versions

    repo = Path(__file__).resolve().parents[4]
    d = (repo / "prompts" / "_base" / "shot_staging"
         / _list_module_versions("shot_staging")[0])
    system = (d / "system.md").read_text(encoding="utf-8")
    schema_text = (d / "schema.json").read_text(encoding="utf-8")
    json.loads(schema_text)  # 유효 JSON 유지
    for needle in ("mid-stride " + "paused", "특정 행동의 " + "정지 순간"):
        assert needle not in system, f"system.md 에 정적 계약 잔존: {needle}"
        assert needle not in schema_text, (
            f"schema.json 에 정적 계약 잔존: {needle}")
    assert "caught mid-stride" in system
