"""참조 **개수** 상한 — provider 능력 계약 **한 벌**. ★유료 0.

Codex 2026-08-31:

> 임의 숫자를 안 박고 **고른 provider 의 capability 계약**을 SOT 로 삼아라.
> preflight 와 보내기 직전이 **같은 method** 를 소비하고, provider
> class/model + capability 를 **lock 에 접어라**. Gemini/Grok 의 공식 max 를
> 모르면 `max=None` — **발명 금지**.

★이미지 **생성 호출** 상한(canary 1회)과 **참조 개수**는 **별개**다.
"""
from __future__ import annotations

import pytest

from app.modules.llm.gemini_image_client import GeminiImageClient
from app.modules.llm.grok_image_client import GrokImageClient
from app.modules.llm.reve_image_client import ReveImageClient
from app.modules.pipeline import grounding_reference_bundle as gb


def _client(C, model="m"):
    c = C.__new__(C)
    c._model = model
    return c


class TestTheClientItselfDeclaresIt:
    """★registry 를 베끼지 않는다 — **실제 instance** 가 낸다."""

    @pytest.mark.parametrize("C", [GeminiImageClient, GrokImageClient,
                                   ReveImageClient])
    def test_every_client_declares_the_contract(self, C):
        cap = gb.capability_of(_client(C))
        for k in ("provider", "model", "supports_labeled_refs",
                  "min_images", "max_images"):
            assert k in cap, f"★{C.__name__} 에 {k} 가 없다"

    def test_grok_inherits_the_same_contract(self):
        """★같은 list 계약이면 **상속**이 정직한 답이다 — 베끼지 않는다."""
        g = gb.capability_of(_client(GeminiImageClient))
        k = gb.capability_of(_client(GrokImageClient))
        assert (g["min_images"], g["max_images"]) == \
            (k["min_images"], k["max_images"])
        assert g["provider"] != k["provider"], "★어느 client 인지는 갈려야 한다"

    def test_gemini_does_not_invent_a_max(self):
        """★★`None` 은 「**모른다**」다 — 「무제한」이 아니다.

        이 client 는 참조를 돌면서 다 싣고 로컬 상한을 **선언하지 않는다**
        (실측). 공식 상한을 모르면 **발명하지 않는다**.
        """
        assert gb.capability_of(_client(GeminiImageClient))["max_images"] is None

    def test_reve_says_exactly_one(self):
        """★`reve/2.1/edit` 의 입력이 `image_url` **단수**다(실측)."""
        cap = gb.capability_of(_client(ReveImageClient))
        assert (cap["min_images"], cap["max_images"]) == (1, 1)

    def test_a_client_without_the_contract_stops(self):
        """★모르는 채로 **안 보낸다**."""
        with pytest.raises(gb.ReferenceCountRefused):
            gb.capability_of(object())


class TestTheCountIsCheckedBeforeSending:
    def test_reve_refuses_more_than_one(self):
        cap = gb.capability_of(_client(ReveImageClient))
        with pytest.raises(gb.ReferenceCountRefused) as e:
            gb.assert_reference_count(cap, 3)
        assert "조용히 자르지 않는다" in str(e.value)

    def test_reve_refuses_zero(self):
        cap = gb.capability_of(_client(ReveImageClient))
        with pytest.raises(gb.ReferenceCountRefused):
            gb.assert_reference_count(cap, 0)

    def test_reve_accepts_exactly_one(self):
        gb.assert_reference_count(gb.capability_of(_client(ReveImageClient)), 1)

    @pytest.mark.parametrize("n", [0, 1, 3, 99])
    def test_an_unknown_max_does_not_block(self, n):
        """★★상한을 **발명하지 않는다** — 모르면 막지 않고 부르는 쪽이 **기록**한다."""
        gb.assert_reference_count(
            gb.capability_of(_client(GeminiImageClient)), n)

    def test_a_declared_max_is_enforced(self):
        cap = {"provider": "X", "min_images": 0, "max_images": 2,
               "supports_labeled_refs": True}
        gb.assert_reference_count(cap, 2)
        with pytest.raises(gb.ReferenceCountRefused):
            gb.assert_reference_count(cap, 3)

    def test_a_client_that_takes_no_labeled_refs_stops(self):
        cap = {"provider": "X", "supports_labeled_refs": False,
               "min_images": 0, "max_images": None}
        gb.assert_reference_count(cap, 0)
        with pytest.raises(gb.ReferenceCountRefused):
            gb.assert_reference_count(cap, 1)


class TestTheLockFoldsProviderAndCapability:
    """★provider 를 바꾸면 **다른 산출**이다 — 지문이 그것을 봐야 한다."""

    def test_a_different_provider_moves_the_lock(self):
        a = gb.capability_lock(gb.capability_of(_client(GeminiImageClient)))
        b = gb.capability_lock(gb.capability_of(_client(ReveImageClient)))
        assert a != b

    def test_a_different_model_moves_the_lock(self):
        a = gb.capability_lock(
            gb.capability_of(_client(GeminiImageClient, "m1")))
        b = gb.capability_lock(
            gb.capability_of(_client(GeminiImageClient, "m2")))
        assert a != b

    def test_the_capability_itself_is_in_the_lock(self):
        lock = gb.capability_lock({"provider": "X", "model": "m",
                                   "supports_labeled_refs": True,
                                   "min_images": 1, "max_images": 4})
        assert lock["min_images"] == 1 and lock["max_images"] == 4

    def test_it_is_deterministic(self):
        c = _client(GeminiImageClient)
        assert gb.capability_lock(gb.capability_of(c)) == \
            gb.capability_lock(gb.capability_of(c))


class TestTheSameMethodIsUsedEverywhere:
    """★preflight 와 보내기 직전이 **같은 것**을 봐야 두 벌이 안 된다."""

    def test_the_checker_reads_through_capability_of(self):
        import inspect

        src = inspect.getsource(gb.assert_reference_count)
        assert "max_images" in src and "min_images" in src
        # ★숫자를 코드에 안 박는다
        assert "== 1" not in src and "<= 2" not in src

    def test_no_number_is_hardcoded_in_the_contract_reader(self):
        import ast
        import inspect
        import textwrap

        tree = ast.parse(textwrap.dedent(inspect.getsource(gb.capability_of)))
        nums = [n.value for n in ast.walk(tree)
                if isinstance(n, ast.Constant) and isinstance(n.value, int)
                and not isinstance(n.value, bool)]
        assert not nums, f"★상한을 코드에 박았다: {nums}"
