"""★배경 사슬 네 스텝의 계측 단위 — loop 를 읽어 선언했고, fixture 상한이 production 산출과 provider 앞에서 대조된다.

background_master_plan(chain_bg 그룹마다) · floor_plan_prompt(floor plan 마다) · floor_plan_render(floor plan 마다 이미지) ·
background_prompt(background 마다). 통합 canary(배경 on → scene_detail)의 전제: 앞 판 dry 에서 넷이 unverified 라 live 문이 안 열렸다."""
from __future__ import annotations

import json

import pytest

from tools.grounding_audit import canary_run as cr


def _cp(root, step, data):
    d = root / "projects" / "p" / "checkpoints" / "episodes" / "e" / step
    d.mkdir(parents=True, exist_ok=True)
    (d / "manifest.json").write_text(json.dumps({"data": data}), encoding="utf-8")


@pytest.fixture
def run_root(tmp_path, monkeypatch):
    monkeypatch.setattr(cr.ci, "root_dir", lambda rid: tmp_path)
    return tmp_path


class TestTheUnitsAreDeclaredFromTheLoops:
    def test_the_four_steps_are_no_longer_unverified(self):
        assert cr.METERING_UNITS["background_master_plan"] == cr.UNIT_CHAIN_GROUP
        assert cr.METERING_UNITS["floor_plan_prompt"] == cr.UNIT_FLOOR_PLAN
        assert cr.METERING_UNITS["floor_plan_render"] == cr.UNIT_FLOOR_PLAN
        assert cr.METERING_UNITS["background_prompt"] == cr.UNIT_BACKGROUND

    def test_the_caps_come_from_the_fixture_and_flow_into_the_logical_cap(self):
        dims = cr.fixture_dimensions("period_episode")
        assert dims["chain_bg_group_cap"] == 3 and dims["floor_plan_cap"] == 6 and dims["background_cap"] == 12
        assert cr.logical_cap_of("background_master_plan", dims) == 3
        assert cr.logical_cap_of("floor_plan_prompt", dims) == 6 == cr.logical_cap_of("floor_plan_render", dims)
        assert cr.logical_cap_of("background_prompt", dims) == 12

    def test_an_undeclared_cap_stops(self):
        with pytest.raises(cr.ScopeMismatch):
            cr.logical_cap_of("background_prompt", {"background_cap": None})

    def test_the_render_step_buys_images_not_text(self):
        # ★뒤집음 (실측 07:18): 이미지 호출도 스텝 문을 지나므로 단위마다 3(이미지 1 + 재시도 2) — 이미지 문 (1,3) 과 같은 수
        assert cr.calls_per_unit_of("floor_plan_render") == 3 and cr.normal_calls_per_unit_of("floor_plan_render") == 1
        assert cr.image_calls_per_unit_of("floor_plan_render") == (1, 3)
        dims = cr.fixture_dimensions("period_episode")
        rows = {r["step"]: r for r in cr.image_plan(dims, ["floor_plan_render"])["rows"]}
        assert rows["floor_plan_render"]["normal"] == 6 and rows["floor_plan_render"]["hard"] == 18

    def test_the_full_background_closure_has_no_unverified_unit(self, monkeypatch):
        monkeypatch.setattr(cr, "axis_actual", lambda axis: {"background": "on", "still_recipe": "off", "outdoor": "on"}[axis])
        sc = cr.scenario(mode="v2_chunk", fixture="period_episode", target="scene_detail",
                         background="on", still_recipe="off", outdoor="on")
        built = cr.build_plan(sc, run_id=None)
        assert built["unverified_units"] == []


class TestTheGatesReadTheProducerOutput:
    def test_chain_groups_use_productions_predicate(self, run_root):
        _cp(run_root, "background_classify", {"building_groups": [
            {"group_id": "a", "kind": "chain_bg"}, {"group_id": "b", "kind": "chain_bg"}, {"group_id": "c", "kind": "single_bg"}]})
        got = cr.assert_chain_group_cap_covers("r", {"chain_bg_group_cap": 3}, set(), ["background_master_plan"])
        assert got["checked"] is True and got["chain_bg_groups"] == 2
        _cp(run_root, "background_classify", {"building_groups": [{"kind": "chain_bg"}] * 4})
        with pytest.raises(cr.ScopeMismatch):
            cr.assert_chain_group_cap_covers("r", {"chain_bg_group_cap": 3}, set(), ["background_master_plan"])

    def test_floor_plans_and_backgrounds_are_counted_across_groups(self, run_root):
        _cp(run_root, "background_master_plan", {"plans": {
            "a": {"plan": {"floor_plans": [{"fp_id": "f1"}, {"fp_id": "f2"}], "backgrounds": [{"bg_id": "b1"}]}},
            "b": {"plan": {"floor_plans": [{"fp_id": "f3"}], "backgrounds": [{"bg_id": "b2"}, {"bg_id": "b3"}]}}}})
        fp = cr.assert_floor_plan_cap_covers("r", {"floor_plan_cap": 6}, set(), ["floor_plan_prompt", "floor_plan_render"])
        bg = cr.assert_background_cap_covers("r", {"background_cap": 12}, set(), ["background_prompt"])
        assert fp["floor_plans"] == 3 and bg["backgrounds"] == 3
        with pytest.raises(cr.ScopeMismatch):
            cr.assert_floor_plan_cap_covers("r", {"floor_plan_cap": 2}, set(), ["floor_plan_prompt"])

    def test_without_the_producer_cp_the_declared_value_stands(self, run_root):
        got = cr.assert_background_cap_covers("r", {"background_cap": 12}, set(), ["background_prompt"])
        assert got["checked"] is False and got["background_cap"] == 12

    def test_all_three_units_are_in_the_producer_gate_table(self):
        for u in (cr.UNIT_CHAIN_GROUP, cr.UNIT_FLOOR_PLAN, cr.UNIT_BACKGROUND):
            assert callable(getattr(cr, cr.PRODUCER_CAP_GATES[u]))
