"""새 sidecar 산출을 **재개 신원이 아나**. ★유료 0.

Codex BLOCK (2026-09-02):

> `_config_hash` 가 `SIDECAR_WRITER_CONTRACT_VERSION` / bundle projection /
> bundle contract 를 전혀 접지 않습니다.

★접는 것은 **넷**이다 — sidecar writer · bundle projection · 중앙 투영 ·
그리고 **묶는 규칙 자체**(`BUNDLE_CONTRACT_VERSION`, Codex NON-BLOCK 09-02).
앞 판 머리말은 셋만 적어 실제와 어긋났다. 따라서 이미 completed 인
> `scene_detail` CP 는 새 deterministic postprocess 를 안 타고, sidecar 없는
> 옛 카드를 그대로 재사용할 수 있습니다. cutover 가 코드에만 있고 기존
> 프로젝트에는 안 닿습니다.
"""
from __future__ import annotations

import pytest

from app.core.steps.detail_steps import SceneDetailStep
from app.modules.pipeline import grounding_bundle_projection as bp
from app.modules.pipeline import grounding_central_acquisition as ca
from app.modules.pipeline import grounding_reference_bundle as rb
from app.modules.pipeline import grounding_sidecar_writer as sw


def _step(mode, *, pid="p", eid="e"):
    """★★2026-09-02: `config_hash` 가 **사람 판정 묶음의 지문**도 접는다 —
    사람이 마음을 바꾸면 옛 카드를 안 되쓰기 위해서다. 그래서 session 이
    있어야 한다(production 스텝은 늘 갖고 있다)."""
    from app.core.database import Base, SessionLocal, engine

    Base.metadata.create_all(bind=engine)
    s = SceneDetailStep.__new__(SceneDetailStep)
    s.project_config = {"grounding_mode": mode} if mode else {}
    s.project_id, s.episode_id = pid, eid
    s.db = SessionLocal()
    s._vca_printed_prop_context = lambda: {}
    s._vca_immobilized_subject_context = lambda: {}
    return s


class TestTheIdentityMovesWhenTheContractMoves:

    def test_the_chunk_run_has_a_different_identity(self):
        assert _step("v2_chunk")._config_hash() != _step("v2")._config_hash()

    def test_a_new_writer_contract_moves_it(self, monkeypatch):
        before = _step("v2_chunk")._config_hash()
        monkeypatch.setattr(sw, "SIDECAR_WRITER_CONTRACT_VERSION", "9.9")
        assert _step("v2_chunk")._config_hash() != before

    def test_a_new_bundle_projection_moves_it(self, monkeypatch):
        before = _step("v2_chunk")._config_hash()
        monkeypatch.setattr(bp, "PROJECTION_CONTRACT_VERSION", "9.9")
        assert _step("v2_chunk")._config_hash() != before

    def test_a_new_acquisition_projection_moves_it(self, monkeypatch):
        before = _step("v2_chunk")._config_hash()
        monkeypatch.setattr(ca, "ACQUISITION_PROJECTION_VERSION", "9.9")
        assert _step("v2_chunk")._config_hash() != before

    def test_a_new_bundle_rule_moves_it(self, monkeypatch):
        """★묶는 규칙(주인 하나 자리·dedupe 열쇠·값 모양)이 바뀌면 움직인다."""
        before = _step("v2_chunk")._config_hash()
        monkeypatch.setattr(rb, "BUNDLE_CONTRACT_VERSION", "9.9")
        assert _step("v2_chunk")._config_hash() != before


class TestTheOldRunsAreByteIdentical:
    """★★음성 대조 — 안 켠 판은 지문이 **한 글자도** 안 움직인다."""

    @pytest.mark.parametrize("mode", [None, "legacy", "v2", "shadow_plan"])
    def test_the_contract_is_not_folded_there(self, mode, monkeypatch):
        before = _step(mode)._config_hash()
        monkeypatch.setattr(sw, "SIDECAR_WRITER_CONTRACT_VERSION", "9.9")
        monkeypatch.setattr(bp, "PROJECTION_CONTRACT_VERSION", "9.9")
        monkeypatch.setattr(ca, "ACQUISITION_PROJECTION_VERSION", "9.9")
        monkeypatch.setattr(rb, "BUNDLE_CONTRACT_VERSION", "9.9")
        assert _step(mode)._config_hash() == before, (
            "★안 켠 판의 지문이 움직였다 — 옛 CP 가 통째로 stale 이 된다")

    def test_all_the_old_modes_share_one_identity(self):
        got = {_step(m)._config_hash()
               for m in (None, "legacy", "v2", "shadow_plan")}
        assert len(got) == 1, f"★옛 판끼리 갈렸다: {got}"


class TestResumeDoesNotReuseTheOldCard:
    """★★★끝점 — 옛 지문의 `completed` CP 는 켠 판에서 **재사용되지 않는다**."""

    def test_the_stored_hash_no_longer_matches(self):
        old_cp = {"status": "completed",
                  "config_hash": _step("v2")._config_hash(),
                  "data": {"render_prompt_card": {"asset_requirements": {}}}}
        assert old_cp["config_hash"] != _step("v2_chunk")._config_hash(), (
            "★옛 카드가 켠 판에서 current 로 읽힌다 — sidecar 가 안 생긴다")
