"""다섯 갈래 **공용** 저작 팩. ★유료 0.

Codex BLOCK (2026-08-31) 넷을 닫는다 —

    1차 BLOCK1  야외 구조물 전용 계약으로 다섯 갈래를 태우고 있었다.
    1차 BLOCK2  「고정 낱말 0」 보고가 거짓이었다(영어 목록 다섯이 남아 있었다).
    2차 BLOCK1  그 수리안이 이번엔 **특정 실재 대상을 금지**해 버렸다 —
                「옛날 특정 브랜드 제품」·「잘 알려진 장소」가 고증 대상이면
                **그 정확한 대상**을 찾아야 하는데 generic 으로 바꾸게 했다.
    2차 BLOCK2  2차 검색에서 지역을 버리는데 **지역을 보는 소비자가 0**이다.

★★★**앞 판의 lint 를 걷어냈다.** 「한 문장에 인용된 조각 2개 이상」으로
목록을 잡으려 했는데, **인용부호 없는 쉼표 목록을 전부 놓쳤다** — 정작 내가
쓴 긴 목록들이 그 lint 에서 초록이었다. 거짓 초록을 주는 시험은 없느니만
못하다. 휴리스틱을 더 쌓지 않고 **끝점의 동적 행동**으로 바꿨다:
받은 구체성이 **그대로 나가나**.
"""
from __future__ import annotations

import pytest

from app.modules.pipeline import grounding_ref_brief as grb
from app.modules.pipeline.grounding_entity_contract import owners

#: 구체성이 서로 다른 서술들. ★어느 것도 코드가 갈라 보면 안 된다.
_GENERIC = "머리를 깎는 가게 앞에 세우는 원통 간판"
_EXACT = "어느 회사가 1962년에 내놓은 그 특정 기종의 원통 간판"


def _build(desc: str, owner: str = "prop", **kw):
    return grb.build_brief_user(
        owner_type=owner, coarse_type_label="부류",
        subject_description=desc, world_facts_block="W",
        era_declaration="1960년대", region_declaration="대한민국",
        source_text="원고", **kw)


class TestSpecificityIsPreserved:
    """★★받은 구체성을 **바꾸지 않는다** (Codex 2차 BLOCK 1).

    generic 이면 generic 을, 실재하는 정확한 대상이면 그 대상을 찾는다.
    코드도 지문도 「흔한가 유명한가」를 짐작하지 않는다.
    """

    @pytest.mark.parametrize("desc", [_GENERIC, _EXACT])
    def test_the_description_goes_out_verbatim(self, desc):
        assert desc in _build(desc), "★서술이 그대로 안 나간다"

    def test_the_two_blocks_differ_only_by_the_description(self):
        """★★구체성으로 **갈라지지 않는다** — 글자까지 같아야 한다."""
        a = _build(_GENERIC).replace(_GENERIC, "<D>")
        b = _build(_EXACT).replace(_EXACT, "<D>")
        assert a == b, "★구체성에 따라 다른 것이 나간다"

    def test_the_builder_never_inspects_the_description(self):
        """★AST — 서술을 뜯어보거나 모델에 물어 구체성을 판단하지 않는다."""
        import ast
        import inspect
        import textwrap

        tree = ast.parse(textwrap.dedent(
            inspect.getsource(grb.build_brief_user)))
        called = {getattr(n.func, "attr", getattr(n.func, "id", ""))
                  for n in ast.walk(tree) if isinstance(n, ast.Call)}
        assert not (called & {"call_structured", "ask_both", "search",
                              "findall", "match", "sub", "split"}), \
            f"★서술을 뜯어본다: {sorted(called)}"

    def test_the_pack_does_not_forbid_a_real_particular_subject(self):
        """★★**뒤집은 시험** — 앞 판 v1 이 정확히 이것을 금지했다.

        v1 의 「Search for the ORDINARY KIND, never a particular one」이
        사용자가 예외 통로의 대표로 든 것을 지웠다. positive control 로
        v1 을 태워 이 시험이 실제로 잡는지 본다.
        """
        cur = grb.load_brief_system()
        assert "never a particular one" not in cur
        old = grb.load_brief_system(prompt_version="1")
        assert "never a particular one" in old, \
            "★양성 대조가 안 걸린다 — 이 시험은 아무것도 안 잰다"


class TestBothCoordinatesRideEveryQuery:
    """★2차 BLOCK 2 — 지역을 버리면 **아무도 안 본다**."""

    def test_the_region_and_the_era_both_go_out(self):
        u = _build(_GENERIC)
        assert "1960년대" in u and "대한민국" in u

    def test_the_narrow_hint_keeps_both(self):
        h = grb.load_narrow_retry_hint()
        assert "THE PERIOD AND THE REGION ARE NOT QUALIFIERS" in h

    def test_the_narrow_hint_drops_neither(self):
        """★★**뒤집은 시험** — v1 은 지역을 버리라고 했다.

        버리라는 절**만** 본다. 대시·keep 뒤는 지키라는 절이다.
        """
        import re

        def _drop_clause(text):
            m = re.search(r"What goes is (.+?)(?:\.|—)", text, re.S) \
                or re.search(r"Drop the\s+(.+?)(?:—|keep\b|\.)", text, re.S)
            return m.group(1) if m else ""

        cur = _drop_clause(grb.load_narrow_retry_hint())
        assert cur, "★무엇을 버리라는 문장이 없다"
        assert "region" not in cur and "period" not in cur \
            and "era" not in cur, f"★좌표를 버리라고 한다: {cur!r}"
        old = _drop_clause(grb.load_narrow_retry_hint(prompt_version="1"))
        assert "region" in old, \
            "★양성 대조가 안 걸린다 — 이 시험은 아무것도 안 잰다"

    def test_the_narrow_hint_keeps_the_specificity_too(self):
        assert "exact" in grb.load_narrow_retry_hint()


class TestThePackIsOwnerAgnostic:
    """★1차 BLOCK 1 — 다섯 갈래가 **같은 계약**을 탄다."""

    def test_every_owner_builds(self):
        for o in sorted(owners()):
            assert o in _build(_GENERIC, owner=o), f"★{o} 가 안 실린다"

    def test_the_blocks_are_identical_except_the_runtime_values(self):
        """★★어느 갈래도 **특별대우 받지 않는다** — 갈래 이름만 빼면 같다."""
        got = {o: _build(_GENERIC, owner=o) for o in sorted(owners())}
        skeleton = {o: t.replace(o, "<OWNER>") for o, t in got.items()}
        assert len(set(skeleton.values())) == 1, \
            f"★갈래마다 다른 문안이 나간다: {sorted(skeleton)}"

    def test_it_refuses_an_owner_outside_the_enum(self):
        with pytest.raises(grb.UnknownOwnerType):
            _build(_GENERIC, owner="스스로 지어낸 갈래")

    def test_the_builder_does_not_branch_on_the_owner(self):
        """★AST — 갈래를 글자로 비교해 문안을 고르면 안 된다."""
        import ast
        import inspect
        import textwrap

        tree = ast.parse(textwrap.dedent(
            inspect.getsource(grb.build_brief_user)))
        for n in ast.walk(tree):
            if not isinstance(n, ast.Compare):
                continue
            names = {getattr(x, "id", "") for x in ast.walk(n)}
            if "ot" not in names and "owner_type" not in names:
                continue
            assert any(isinstance(op, (ast.In, ast.NotIn)) for op in n.ops), \
                "★갈래를 글자로 갈라 문안을 고른다"


class TestTheOldStructurePackIsUntouched:
    """★야외 구조물 경로는 **그대로** 둔다 — 보존이 계약이다."""

    def test_the_outdoor_pack_still_loads(self):
        from app.modules.pipeline.search_grounded_ref import (
            build_search_brief_user, load_brief_system)

        assert "structure" in load_brief_system().lower()
        assert "STRUCTURE" in build_search_brief_user(
            structure_desc="D", world_facts_block="W", source_text="S")

    def test_the_two_packs_are_different_files(self):
        from app.modules.pipeline.search_grounded_ref import (
            load_brief_system as outdoor)

        assert grb.load_brief_system() != outdoor()

    def test_the_schema_is_shared_not_copied(self):
        """★산출 모양은 **한 곳**이다 — 두 벌이면 한쪽만 고쳐진다."""
        from app.modules.pipeline.search_grounded_ref import (
            build_search_brief_schema)

        assert grb.build_brief_schema() == build_search_brief_schema()

    def test_the_old_general_version_is_kept_not_overwritten(self):
        """★프롬프트 파일은 덮어쓰지 않는다 — 새 버전 디렉토리로 간다."""
        assert set(grb.PACK_VERSION_MAP) >= {"1", "2", "3"}
        # ★2026-09-02 판 3 — 시대·지역을 **독립**으로 다루고 언어 단정을
        #  걷었다. 옛 판 둘은 **그대로 남아 있어야** 한다(이 시험의 뜻).
        # ★2026-09-02 저녁 판 4 — 뼈대만 찾는다 · 실물 사진(소장품·복원품) 허용 · 2차는 넓히되
        #  좌표 유지. 옛 판 셋은 **그대로 남아 있어야** 한다.
        # ★2026-09-02 밤 판 5 — 4 의 「현대 장소」문단에서 고정 명사 예시를 뺐다(Codex BLOCK).
        assert grb.PACK_VERSION == "5"
        assert grb.load_brief_system(prompt_version="1") != \
            grb.load_brief_system(prompt_version="2")
        assert grb.load_brief_system(prompt_version="3") != \
            grb.load_brief_system(prompt_version="4")
        assert grb.load_brief_system(prompt_version="4") != \
            grb.load_brief_system(prompt_version="5")


class TestTheCanaryActuallySendsTheGeneralPack:
    """★★**끝점에서** 잰다 — 조립부만 보면 또 놓친다.

    `build_brief_user` 가 옳아도 `ref_canary._write_brief` 가 옛 팩을 부르면
    나가는 것은 야외 구조물 계약이다. **나가는 것**을 붙잡아 본다.
    """

    @staticmethod
    def _capture(monkeypatch):
        seen = {}

        def _fake(tag, system, user, schema, **kw):
            seen.update(tag=tag, system=system, user=user, schema=schema)
            return {"source_language": "한국어",
                    "search_directive_native": "찾아라" * 20,
                    "search_terms_native": ["가", "나", "다"],
                    "language_lock_native": "원어로만"}

        import app.modules.llm.llm_client as lc
        monkeypatch.setattr(lc, "call_structured", _fake)
        return seen

    def _send(self, monkeypatch, owner, *, narrow=False, desc=_GENERIC):
        from tools.grounding_audit import ref_canary as rc

        seen = self._capture(monkeypatch)
        rc._write_brief(
            {"owner_type": owner, "surface_form": desc,
             "coarse_type_label": "부류", "visual_brief": ""},
            world_facts="세계", source_text="원고", narrow=narrow,
            era_declaration="1960년대", region_declaration="대한민국")
        return seen

    def test_a_prop_is_not_sent_as_a_place_structure(self, monkeypatch):
        seen = self._send(monkeypatch, "prop")
        assert "THE STRUCTURE that must be built" not in seen["user"], \
            "★소품을 「지어야 할 구조물」로 보낸다"
        assert "prop" in seen["user"] and "부류" in seen["user"]

    def test_both_coordinates_actually_go_out(self, monkeypatch):
        seen = self._send(monkeypatch, "location")
        assert "1960년대" in seen["user"], "★시대가 안 나간다"
        assert "대한민국" in seen["user"], "★지역이 안 나간다"

    def test_an_exact_referent_goes_out_unchanged(self, monkeypatch):
        seen = self._send(monkeypatch, "prop", desc=_EXACT)
        assert _EXACT in seen["user"], "★정확한 대상이 그대로 안 나간다"

    def test_every_owner_goes_out_through_the_same_pack(self, monkeypatch):
        for o in sorted(owners()):
            seen = self._send(monkeypatch, o)
            assert seen["system"] == grb.load_brief_system(), f"★{o}"
            assert o in seen["user"], f"★{o} 가 안 실린다"

    def test_the_system_prompt_is_not_the_outdoor_one(self, monkeypatch):
        from app.modules.pipeline.search_grounded_ref import (
            load_brief_system as outdoor)

        seen = self._send(monkeypatch, "character")
        assert seen["system"] != outdoor(), "★야외 구조물 지문이 나간다"

    def test_the_narrow_round_appends_the_general_hint(self, monkeypatch):
        seen = self._send(monkeypatch, "outlook", narrow=True)
        assert grb.load_narrow_retry_hint() in seen["user"]
