"""질의에 **시대 좌표**가 붙었나. ★유료 0. 뜻을 판단하지 않는다 — 글자만 본다.

사용자 실측 (2026-08-31): 찾아온 참조가 **그 시대보다 오래된 것**이었다.
질의를 보니 「옛날·전통·민속자료」가 섞이고 **시대 좌표가 빠진 것이 많았다** —
기준선 측정: 질의 110개 중 시대가 붙은 것 **54개(49%)**.

기록물은 주어진 시기보다 **앞선** 시기가 훨씬 많이 남아 있다. 그래서 시대를
안 붙이면 더 오래된 것이 온다.

★심판에게 시대를 묻지 않는 것이 원칙이므로(사용자 확정), 시대는 **검색이
지켜야 한다**. 여기서 재는 것은 「지시문이 시킨 대로 시대가 붙었나」이지
「이 사진이 그 시대 것인가」가 **아니다** — 뒤의 것은 사람 몫이다.
"""
from __future__ import annotations

import pytest

from app.modules.pipeline import reference_acquisition_rounds as rar


class TestItReadsTheEraFromTheWorldFacts:
    """★코드에 시대를 안 적는다 — 세계 사실에서 온다."""

    def test_it_picks_up_the_year(self):
        assert "1960" in rar.era_tokens_of("1960년대")

    def test_a_different_era_gives_different_tokens(self):
        a = rar.era_tokens_of("1960년대")
        b = rar.era_tokens_of("1890년 무렵")
        assert a != b and "1890" in b

    def test_no_year_gives_nothing(self):
        """★못 뽑으면 **빈 목록** — 「없다」를 「통과」로 안 읽는다."""
        assert rar.era_tokens_of("먼 옛날의 어느 곳") == []

    def test_the_code_names_no_era(self):
        """★코드에 시대를 안 적는다 — 세계 사실에서 온다.

        ★★**설명 문장은 빼고 본다.** 앞 판은 docstring 의 「1960」을 잡았다 —
        그러면 왜 그렇게 했는지 설명을 지워야 시험이 통과한다. 이 저장소에서
        여러 번 겪은 부류다.
        """
        import ast
        import inspect
        import textwrap

        tree = ast.parse(textwrap.dedent(
            inspect.getsource(rar.era_tokens_of)))
        for node in ast.walk(tree):
            body = getattr(node, "body", None)
            if isinstance(body, list) and body and \
                    isinstance(body[0], ast.Expr) and \
                    isinstance(body[0].value, ast.Constant) and \
                    isinstance(body[0].value.value, str):
                body.pop(0)            # ★설명을 벗긴다
        for n in ast.walk(tree):
            if isinstance(n, ast.Constant) and isinstance(n.value, str):
                if "\\d" in n.value:
                    continue           # ★정규식 자리표는 시대가 아니다
                assert not any(c.isdigit() for c in n.value), \
                    f"★코드에 시대를 적었다: {n.value!r}"


class TestItCountsWhatCarriesTheEra:
    def test_it_separates_with_and_without(self):
        got = rar.era_coverage(
            ["1960년대 국밥집", "옛날 값판", "한국 전통 됫박"], ["1960"])
        assert got["measured"] is True
        assert (got["total"], got["with_era"], got["without"]) == (3, 1, 2)
        assert "옛날 값판" in got["missing"]

    def test_no_tokens_means_it_measures_nothing(self):
        """★★**뒤집은 시험** (Codex BLOCK 2026-08-31).

        앞 판은 `without == 0` 만 확인해서, 잴 근거가 아예 없는데
        `with_era = total` 로 **100% 통과**를 내는 것을 **정답으로 잠갔다**.
        내 시험이 결함을 못박은 자리다. 이제 「안 쟀다」로 서는지 본다.
        """
        got = rar.era_coverage(["아무 질의"], [])
        assert got["measured"] is False, "★잴 근거가 없는데 쟀다고 한다"
        assert got["with_era"] == 0, "★못 쟀는데 통과로 셌다"
        assert got["unknown"] == got["total"] == 1

    def test_it_does_not_judge_meaning(self):
        """★글자만 본다 — 「이 사진이 그 시대 것인가」를 안 묻는다."""
        import ast
        import inspect

        src = inspect.getsource(rar.era_coverage)
        tree = ast.parse(src)
        calls = {getattr(n.func, "attr", getattr(n.func, "id", ""))
                 for n in ast.walk(tree) if isinstance(n, ast.Call)}
        assert not (calls & {"call_structured", "ask_both", "search"}), \
            "★모델에게 물어본다"


class TestTheRoundsRecordIt:
    def test_each_round_carries_the_count(self, tmp_path):
        def _search(**kw):
            return {"queries": [["1960년대 가게 사진", "옛날 가게"]],
                    "images": []}

        got = rar.acquire_one(
            {"subject_id": "x", "directive_native": "찾을 것",
             "terms_native": ["낱말"], "language_lock_native": "ko"},
            workdir=tmp_path, rel_root=tmp_path, search=_search,
            download=lambda u, d, f="": False, judge=lambda c: {},
            era_tokens=["1960"])
        cov = got["rounds"][0]["era_coverage"]
        assert cov["total"] == 2 and cov["with_era"] == 1

    def test_the_runner_passes_the_tokens(self):
        import inspect

        from tools.grounding_audit import ref_canary as rc

        assert "era_tokens=rar.era_tokens_of" in inspect.getsource(rc.main)


# ★★저작 팩의 계약은 **`test_general_ref_brief_pack.py` 한 곳**이 갖는다.
#  앞 판은 여기와 거기 **둘 다** 적었고, 팩을 v2 로 올리자 이쪽만 남아
#  깨졌다 — 같은 규칙을 두 곳에 적으면 한쪽만 고쳐진다. 여기는 **재는
#  도구**(era_coverage·era_tokens_of)만 본다.


class TestAYearInsideTheDecadeCountsToo:
    """★★재는 도구가 **더 좋은 질의를 벌주면** 안 된다.

    실측 (2026-08-31): 「1960」만 찾았더니 `1962년`·`1966년`·`1969년` 처럼
    **더 정확한** 질의가 「시대 없음」으로 세어졌다. 정확히 세니 64% → 92%.
    """

    def test_a_year_in_the_same_decade_is_the_era(self):
        got = rar.era_coverage(["1966년 이발소 사진"], ["1960"])
        assert got["with_era"] == 1

    def test_a_year_in_another_decade_is_not(self):
        """★positive control — 넓히다가 아무 연도나 통과시키면 안 된다."""
        for year in ("1890년", "1935년", "2020년"):
            got = rar.era_coverage([f"{year} 이발소"], ["1960"])
            assert got["with_era"] == 0, f"★{year} 가 통과했다"

    def test_it_is_arithmetic_not_meaning(self):
        """★「같은 십년대인가」는 숫자 계산이지 뜻 판단이 아니다."""
        import ast
        import inspect

        tree = ast.parse(inspect.getsource(rar.era_coverage))
        calls = {getattr(n.func, "attr", getattr(n.func, "id", ""))
                 for n in ast.walk(tree) if isinstance(n, ast.Call)}
        assert not (calls & {"call_structured", "ask_both", "search",
                             "generate"}), "★모델에게 물어본다"

    def test_no_era_token_still_measures_nothing(self):
        """★★**뒤집은 시험** — 앞 판은 `without == 0` 만 봐서 「어긋남 0」을
        「합격」으로 읽게 했다. 잴 근거가 없으면 **비율을 내면 안 된다**.
        """
        got = rar.era_coverage(["1966년 무엇"], [])
        assert got["measured"] is False and got["with_era"] == 0
        assert got["unknown"] == 1


class TestWrongEraIsNotTheSameAsNoEra:
    """★★**시대가 없는 것**과 **시대가 어긋난 것**은 고칠 곳이 다르다.

    실측 (2026-08-31) — 사용자가 「1960년대 이전 것」이라고 지적한 근거가
    앞 판 질의에 그대로 있었다:

        "단기 4294년" "이용 요금표"
        수전 핸리 **1950년대** 한국 농촌 사진 모자
        포천 **1950년대** 노인 털모자 사진

    시대를 안 적은 것이 아니라 **다른 시대**를 적었다. 한 수로 세면 무엇을
    고쳐야 하는지 모른다.
    """

    def test_a_year_outside_the_decade_is_counted_apart(self):
        got = rar.era_coverage(["1973년 이발소", "1950년대 농촌"], ["1960"])
        assert got["without"] == 0, "★시대를 적었는데 「없음」으로 셌다"
        assert got["outside_era"] == 2
        assert got["with_era"] == 0

    def test_all_three_kinds_add_up(self):
        got = rar.era_coverage(
            ["1966년 맞음", "1950년대 어긋남", "옛날 없음"], ["1960"])
        assert (got["with_era"], got["outside_era"], got["without"]) == (1, 1, 1)
        assert got["with_era"] + got["outside_era"] + got["without"] == \
            got["total"]

    def test_the_wrong_ones_are_kept_for_reading(self):
        got = rar.era_coverage(["1950년대 농촌"], ["1960"])
        assert got["outside"] == ["1950년대 농촌"]

    def test_the_new_pack_cut_both_kinds(self):
        """★★얼어붙은 두 판을 견준다 — 이것이 팩 v11 의 근거다."""
        import json
        from pathlib import Path

        from tests.grounding.fixtures import period_episode as ep

        base = Path(__file__).resolve().parents[3] / "artifact" \
            / "20260831_period_canary"
        if not (base / "live_ref.json").exists() or \
                not (base / "v11_ref.json").exists():
            pytest.skip("얼어붙은 두 판이 없다")
        toks = rar.era_tokens_of(ep.ERA)

        def _cov(name):
            d = json.loads((base / name).read_text(encoding="utf-8"))
            q = [x for r in d["records"] for rd in r["rounds"]
                 for g in (rd.get("queries") or [])
                 for x in (g if isinstance(g, list) else [g])]
            return rar.era_coverage(q, toks)

        old, new = _cov("live_ref.json"), _cov("v11_ref.json")
        old_rate = old["with_era"] / old["total"]
        new_rate = new["with_era"] / new["total"]
        assert new_rate > old_rate + 0.2, \
            f"★시대 좌표가 안 늘었다: {old_rate:.0%} → {new_rate:.0%}"
        assert new["outside_era"] < old["outside_era"], \
            "★어긋난 시대가 안 줄었다"
