"""구간 나누기는 **한 곳**이어야 한다. ★유료 0.

같은 묶기 규칙이 다섯 곳에 적혀 있었고 상수도 두 곳에 있었다. 갈리면
**preflight 가 세는 구간과 실제로 굽는 구간이 달라지고**, 그러면 승인한 수와
실제로 사는 수가 안 맞는다.
"""
from __future__ import annotations

import glob
import json
import os

import pytest

from app.modules.pipeline import grounding_chunk_plan as cp


class TestTheConstantIsNotAllowedToDrift:
    def test_the_two_definitions_agree(self):
        cp.assert_bundle_target_agrees()

    def test_it_stops_when_they_disagree(self, monkeypatch):
        """★positive control — 갈렸을 때 정말 서나."""
        import app.core.steps.beat_shot_steps as bss

        monkeypatch.setattr(bss, "BUNDLE_TARGET", cp.BUNDLE_TARGET + 1)
        with pytest.raises(AssertionError, match="두 곳에서 다르다"):
            cp.assert_bundle_target_agrees()


class TestItMatchesTheProductionGrouping:
    """★새 함수가 생산 관례와 **같은 묶음**을 내는가."""

    @staticmethod
    def _production(scene_texts):
        """`entity_lister` 가 쓰는 loop 를 **그 파일에서 그대로** 옮겨 적은 것.

        ★비교 대상이므로 여기서는 두 벌이어도 된다 — 오히려 두 벌이라야
        「같은가」를 물을 수 있다.
        """
        out, cur, n = [], [], 0
        i = 0
        while i < len(scene_texts):
            bundle, bl = [], 0
            while (i < len(scene_texts)
                   and bl + scene_texts[i]["length"] <= cp.BUNDLE_TARGET):
                bundle.append(scene_texts[i]["idx"])
                bl += scene_texts[i]["length"]
                i += 1
            if not bundle and i < len(scene_texts):
                bundle.append(scene_texts[i]["idx"])
                i += 1
            out.append(bundle)
        return out

    @pytest.mark.parametrize("lens", [
        [1200, 900, 1500, 400, 300],
        [5000, 100],                      # ★한 씬이 상한보다 크다
        [3000, 3000, 3000],               # ★딱 맞는다
        [1, 1, 1, 1, 1],
        [2999, 2],
    ])
    def test_synthetic_inputs_group_the_same(self, lens):
        st = [{"idx": i, "length": n} for i, n in enumerate(lens, 1)]
        assert cp.bundle_scenes(st) == self._production(st)

    def test_real_checkpoints_group_the_same(self):
        """★★합성만으로 끝내지 않는다 — **실제 씬 길이**로 맞댄다."""
        here = os.path.dirname(os.path.abspath(__file__))
        root = os.path.abspath(os.path.join(here, "..", "..", "..", "projects"))
        paths = sorted(glob.glob(os.path.join(
            root, "*", "checkpoints", "episodes", "*", "scene_save",
            "manifest.json")))[:20]
        if not paths:
            pytest.skip("실제 체크포인트가 없다")
        n = 0
        for p in paths:
            segs = (json.load(open(p, encoding="utf-8")).get("data")
                    or {}).get("segments") or []
            if not segs:
                continue
            st = [{"idx": int(s.get("scene_index") or 0),
                   "length": len(s.get("text") or "")} for s in segs]
            assert cp.bundle_scenes(st) == self._production(st), \
                f"★{p} 에서 묶음이 갈린다"
            n += 1
        assert n >= 5, f"★맞댄 에피소드가 {n}개뿐 — 빈손은 모든 축을 지난다"


class TestItNeverCutsASceneOrLosesOne:
    def test_every_scene_lands_in_exactly_one_chunk(self):
        st = [{"idx": i, "length": (i * 700) % 4000} for i in range(1, 30)]
        got = cp.bundle_scenes(st)
        flat = [x for b in got for x in b]
        assert flat == [s["idx"] for s in st], "★순서가 바뀌거나 잃었다"
        assert all(got), "★빈 구간이 있다"

    def test_an_oversized_scene_becomes_its_own_chunk(self):
        """★자르지 않는다 — 구간을 더 나눌 뿐이다."""
        st = [{"idx": 1, "length": 10}, {"idx": 2, "length": 99_999},
              {"idx": 3, "length": 10}]
        assert cp.bundle_scenes(st) == [[1], [2], [3]]

    def test_the_length_field_name_can_differ(self):
        """★생산 코드가 `length` 와 `len` 두 이름을 쓴다."""
        st = [{"idx": 1, "len": 2000}, {"idx": 2, "len": 2000}]
        assert cp.bundle_scenes(st, key="len") == [[1], [2]]


class TestTheCcToolsAllUseThisOneFunction:
    """★도구가 제 loop 를 다시 적으면 여섯 벌이 된다."""

    @pytest.mark.parametrize("mod", ["cc_c_preflight", "cc_b_verify"])
    def test_no_cc_tool_reimplements_the_loop(self, mod):
        import ast
        import importlib

        m = importlib.import_module(f"tools.grounding_audit.{mod}")
        src = ast.parse(open(m.__file__, encoding="utf-8").read())
        names = {n.id for n in ast.walk(src) if isinstance(n, ast.Name)}
        assert "BUNDLE_TARGET" not in names, \
            f"★{mod} 가 상한을 제 손으로 쓴다 — 묶기를 다시 적었을 것이다"
        assert "bundle_scenes" in names, f"★{mod} 가 공용 함수를 안 쓴다"
