"""나가는 호출의 **층 계약** — counted 와 raw 는 다르다. ★유료 0.

Codex BLOCK 2 (2026-08-31) —

> `physical_per_logical` 은 key-slot/tier/router retry 만 셉니다. structured
> 경로의 LiteLLM SDK `max_retries=2` 는 reserve **아래**라 raw HTTP 가
> counted 보다 최대 3배입니다. 같은 산식을 새로 쓰지 말고 **공용 계약으로
> 추출해 재사용**하십시오.
"""
from __future__ import annotations

import pytest

from tools.grounding_audit import call_bound_contract as cb

PINNED = {"num_retries": 0, "enable_fallback": False}
LOOSE = {"num_retries": 3, "enable_fallback": True}


class TestTheLayersAreWhatTheContractSays:
    def test_a_pinned_contract_has_one_tier_and_one_try(self):
        lay = cb.layers(contract=PINNED, slots=2)
        assert lay["tiers"] == 1 and lay["router_tries"] == 1
        assert lay["per_logical_counted"] == 2          # 슬롯만 남는다

    def test_fallback_brings_three_tiers(self):
        assert cb.layers(contract=LOOSE, slots=2)["tiers"] == 3

    def test_router_retries_are_below_the_door_not_counted(self):
        """★★Router 의 `num_retries` 는 `router.completion()` **안**이라
        `reserve` **뒤**다 — 세어지지 않는다 (2026-08-31 실측).

        앞 판은 이것을 counted 에 곱해 **막을 수 있는 수를 부풀렸다**.
        """
        lay = cb.layers(contract={"num_retries": 3, "enable_fallback": False},
                        slots=2)
        assert lay["router_tries"] == 4
        assert lay["per_logical_counted"] == 2, "★재시도를 문 위로 셌다"
        assert lay["below_the_door"] == 4 * 3   # router 4 × SDK 3

    def test_tiers_and_slots_are_above_the_door(self):
        """★tier 마다 `_completion` 을 다시 부르고, 슬롯마다 `reserve` 한다."""
        lay = cb.layers(contract=LOOSE, slots=2)
        assert lay["per_logical_counted"] == 3 * 2

    def test_zero_slots_still_counts_one(self):
        """★슬롯이 0이어도 한 번은 나간다 — 0으로 곱하면 거짓이 된다."""
        assert cb.layers(contract=PINNED, slots=0)["per_logical_counted"] == 1


class TestRawIsNotCounted:
    def test_the_sdk_layer_multiplies_raw_only(self):
        """★★litellm 이 제 client 에 주는 재시도는 문 **아래**다."""
        b = cb.bounds(logical=10, contract=PINNED, slots=2)
        assert b["counted"] == 20
        assert b["raw_http"] == 20 * (1 + cb.LITELLM_SDK_MAX_RETRIES) == 60

    def test_locking_the_sdk_makes_them_equal(self):
        """★SDK 재시도를 0으로 잠그면 둘이 같아진다 — 검색 경로가 그렇다."""
        b = cb.bounds(logical=10, contract=PINNED, slots=2,
                      sdk_max_retries=0)
        assert b["counted"] == b["raw_http"] == 20

    def test_the_note_says_which_one_can_be_stopped(self):
        b = cb.bounds(logical=1, contract=PINNED, slots=1)
        assert "막을 수 있는" in b["★means"]
        assert "막을 수 없는" in b["★means"]

    def test_a_loose_contract_raises_counted_by_tiers_only(self):
        """★★계약이 헐거워지면 counted 는 **tier 배(3)**, raw 는 **12배**다.

        재시도는 문 **아래**라 counted 를 안 늘린다 — 그래서 둘이 갈린다.
        """
        a = cb.bounds(logical=1, contract=PINNED, slots=2)
        b = cb.bounds(logical=1, contract=LOOSE, slots=2)
        assert b["counted"] == a["counted"] * 3, "★재시도가 counted 에 섞였다"
        assert b["raw_http"] == a["raw_http"] * 3 * 4


class TestItDoesNotInventWhatItCannotCount:
    def test_unknown_slots_give_no_number(self):
        b = cb.bounds(logical=99, contract=LOOSE, slots=None)
        assert b["counted"] is None and b["raw_http"] is None
        assert "지어내지 않는다" in b["layers"]["why"]

    @pytest.mark.parametrize("bad", [-1, -5])
    def test_a_negative_retry_is_floored(self, bad):
        lay = cb.layers(contract={"num_retries": bad,
                                  "enable_fallback": False}, slots=1)
        assert lay["router_tries"] == 1


class TestBothToolsUseThisOneContract:
    def test_the_cost_table_delegates(self):
        from tools.grounding_audit import canary_cost_table as ct

        assert ct.physical_upper(logical=3, contract=LOOSE, slots=2) == \
            cb.bounds(logical=3, contract=LOOSE, slots=2)

    def test_it_matches_production_only_when_retries_are_pinned(self):
        """★production `physical_per_logical` 은 **재시도까지 곱한다**.

        그 함수의 docstring 이 이미 「재시도나 fallback 을 켜면 이 식이
        틀린다」고 적어 뒀다. 재시도가 0 일 때만 counted 와 같다.
        """
        from app.core.steps.grounding_chunk_step import physical_per_logical

        assert cb.layers(contract=PINNED, slots=2)["per_logical_counted"] == \
            physical_per_logical(PINNED, 2)
        assert cb.layers(contract=LOOSE, slots=2)["per_logical_counted"] < \
            physical_per_logical(LOOSE, 2)

    def test_the_sdk_constant_is_the_same_object_not_a_copy(self):
        """★★같은 **값**이 아니라 같은 **자리**여야 한다.

        값만 견주면 한쪽을 고쳐도 시험이 통과한다 — 그게 「한 규칙 두 곳」이다.
        """
        import ast
        import inspect

        from tools.grounding_audit import ref_canary as rc

        assert rc.LITELLM_SDK_MAX_RETRIES is cb.LITELLM_SDK_MAX_RETRIES
        # ★`ref_canary` 가 제 값을 **다시 적지 않는다** (AST 로 본다)
        tree = ast.parse(inspect.getsource(rc))
        assigned = [t.id for n in ast.walk(tree)
                    if isinstance(n, ast.Assign)
                    for t in n.targets if isinstance(t, ast.Name)]
        assert "LITELLM_SDK_MAX_RETRIES" not in assigned
