"""접두 파서를 **한 계약**으로 바꿔도 기존 값이 그대로인가. ★유료 0.

D cutover 문서 1-B —

> ★단, 접두 counter 수리는 **지금도 값이 바뀐다** — `LP` 가 없어도 파서가
> 바뀌면 기존 `L##` 계산이 같아야 한다. **전후 등가 시험**을 반드시 넣는다.

★앞 판은 `short_id[0]` 로 한 글자만 떼고 `int(short_id[1:])` 를 했다.
`LP01` 은 `int("P01")` 에서 ValueError 가 나 **경고만 남기고 빠졌다** —
그 갈래 counter 가 작아져 **이미 쓴 번호를 다시 발급**한다.
"""
from __future__ import annotations

import pytest

from app.modules.pipeline.grounding_entity_contract import (OWNER_PREFIX,
                                                            split_final_id)


def _old_parser(sid):
    """★고치기 **전** 셈. 등가를 말하려면 옛 셈이 여기 있어야 한다."""
    if not sid or len(sid) <= 1:
        return None
    try:
        return sid[0], int(sid[1:])
    except (ValueError, IndexError):
        return None


def _new_parser(sid):
    if not sid or len(sid) <= 1:
        return None
    got = split_final_id(sid)
    if got is None:
        return None
    owner, num = got
    return OWNER_PREFIX[owner], num


LEGACY = ["C01", "C99", "L01", "L07", "P03", "P12", "O01", "O42",
          "C1", "L100", "P00"]
MALFORMED = ["", "C", "CO01", "C0a", "1C1", "LL01"]
#: ★새 파서가 **더 엄격해진** 자리. 옛 파서는 이것들을 받았다 —
#:  `X01` 은 아무 첫 글자나 받아 없는 갈래 칸을 만들었고, `L-1` 은 음수를
#:  냈다. 둘 다 **counter 결과는 안 바뀐다**(아래 시험이 그것을 잰다).
NOW_STRICTER = ["X01", "L-1"]


def _seed(parser, ids):
    """실제로 하는 일 — counter 를 올린다. ★등가는 **이 결과**로 잰다.

    ★**읽히는 갈래만** 센다. 옛 파서는 `X01` 같은 것에 `X` 칸을 새로
    만들었는데, 번호를 발급하는 쪽은 `_TYPE_DEFS` 의 접두만 보므로 그 칸은
    **아무도 안 읽는다**. 그것까지 세면 「값이 달라졌다」로 잘못 읽는다.
    """
    known = set(OWNER_PREFIX.values())
    counters = {p: 0 for p in known}
    for sid in ids:
        got = parser(sid)
        if got is None:
            continue
        prefix, num = got
        if prefix not in known:
            continue
        counters[prefix] = max(counters[prefix], num)
    return {k: v for k, v in counters.items() if v}


class TestTheOldNumbersDoNotMove:
    @pytest.mark.parametrize("sid", LEGACY)
    def test_a_legacy_id_parses_the_same(self, sid):
        assert _new_parser(sid) == _old_parser(sid), f"★{sid} 의 셈이 달라졌다"

    @pytest.mark.parametrize("sid", MALFORMED)
    def test_a_malformed_id_is_still_skipped(self, sid):
        assert _new_parser(sid) is None and _old_parser(sid) is None

    def test_the_counters_come_out_identical(self):
        """★★재는 것은 파싱 값이 아니라 **counter 결과**다."""
        ids = LEGACY + MALFORMED + NOW_STRICTER
        assert _seed(_new_parser, ids) == _seed(_old_parser, ids)

    @pytest.mark.parametrize("sid", NOW_STRICTER)
    def test_the_stricter_cases_never_moved_a_counter(self, sid):
        """★새 파서가 거절하는 두 자리는 **옛 셈에서도 값을 안 올렸다**.

        `X01` → 없는 갈래 칸을 만들 뿐 실제 갈래에 안 닿았고,
        `L-1` → 음수라 `max` 가 무시했다. 그래서 더 엄격해져도 무해하다.
        """
        assert _old_parser(sid) is not None, "★전제가 틀렸다 — 옛 셈이 거절했다"
        assert _new_parser(sid) is None
        # ★실제로 번호를 발급하는 갈래에는 **한 칸도 안 닿았다**
        assert _seed(_old_parser, [sid]) == _seed(_new_parser, [sid]) == {}

    def test_every_single_letter_owner_is_covered(self):
        """★한 글자 접두 갈래는 **전부** 옛 셈과 같아야 한다."""
        for owner, p in OWNER_PREFIX.items():
            if len(p) != 1:
                continue
            for n in (1, 9, 10, 99):
                sid = f"{p}{n:02d}"
                assert _new_parser(sid) == _old_parser(sid) == (p, n)


class TestTheTwoLetterOwnerNoLongerVanishes:
    @pytest.mark.parametrize("n", [1, 7, 42])
    def test_a_location_part_id_now_counts(self, n):
        """★★`LP01` 이 옛 셈에서는 **사라졌다**. 이제 제 자리로 센다."""
        sid = f"LP{n:02d}"
        assert _old_parser(sid) is None, "★옛 셈이 이미 읽었다면 전제가 틀렸다"
        assert _new_parser(sid) == ("LP", n)

    def test_it_does_not_steal_the_location_counter(self, ):
        """★`LP07` 이 `L` 을 7 로 올리면 **`L07` 을 다시 발급**한다."""
        assert _new_parser("LP07")[0] == "LP"
        assert _new_parser("L07") == ("L", 7)


class TestTheServiceUsesTheOneContract:
    def test_no_second_prefix_table_in_the_service(self):
        """★접두 글자를 서비스가 **따로 적지 않는다** (AST 로 본다).

        ★글자 검색은 설명을 적은 주석을 위반으로 읽는다
        ([[feedback-my-guard-caught-its-own-explanation]]).
        """
        import ast
        import inspect

        from app.services.checkpoint_sync import entity_sync_service as svc

        tree = ast.parse(inspect.getsource(svc))
        letters = set(OWNER_PREFIX.values())
        literal_maps = [
            n for n in ast.walk(tree)
            if isinstance(n, ast.Dict) and n.keys
            and all(isinstance(k, ast.Constant) and isinstance(k.value, str)
                    for k in n.keys)
            and {v.value for v in n.values
                 if isinstance(v, ast.Constant)} & letters
        ]
        assert not literal_maps, "★접두 표가 서비스 안에 또 있다"

    def test_the_service_imports_the_contract(self):
        from app.services.checkpoint_sync import entity_sync_service as svc

        assert svc.OWNER_PREFIX is OWNER_PREFIX
        assert svc.split_final_id is split_final_id
