"""곁가지 칸 하나에 계획 전체를 버리지 않는다 (실측 3회, 2026-09-05).

## 무엇이 결함이었나

모델이 `shot_plans` 항목 안에 `group_key` 를 하나 더 넣었다. 스키마가
`additionalProperties: False` 라 **멀쩡한 계획 전체가 버려지고** 스텝이 죽었다
(`shot_plans/S9sh5` 1화 · `shot_plans/S7sh4` 2화, 세 주행에서 반복).

더 나쁜 것은 **재시도가 안 돌았다**는 점이다. `run_background_share_plan` 은
`max_attempts=3` 으로 위반을 되먹여 고쳐 쓰게 돼 있는데, 스키마 위반만은
`call_structured` 안에서 예외로 터져 그 루프를 **통째로 건너뛰었다.** 그래서
한 번 만에 죽었다.

## 고친 계약

1. `shot_plans` 항목의 뜻은 세 칸(`ref_plan`·`prev_anchor_tag`·`rationale_ko`)
   으로 온전히 정해진다. 모르는 칸은 **떼어 내고 기록한다** — 조용히 통과시키지도,
   계획을 버리지도 않는다.
2. 스키마 위반도 **재시도 루프 안에서** 다른 위반과 똑같이 고쳐 쓴다.
"""
from __future__ import annotations

import pytest

from app.modules.pipeline import background_share_plan as bsp


def test_곁가지_칸은_떼어내고_기록한다():
    r = {"shot_plans": {
        "S7sh4": {"ref_plan": "background", "group_key": "bg_x",
                  "rationale_ko": "이유"},
        "S7sh5": {"ref_plan": "prev", "prev_anchor_tag": "S7sh4"}}}
    dropped = bsp.normalize_shot_plans(r)
    assert dropped == {"S7sh4": ["group_key"]}, dropped
    assert sorted(r["shot_plans"]["S7sh4"]) == ["rationale_ko", "ref_plan"]
    # ★멀쩡한 항목은 안 건드린다
    assert r["shot_plans"]["S7sh5"] == {"ref_plan": "prev",
                                        "prev_anchor_tag": "S7sh4"}


def test_뗄_것이_없으면_아무것도_안_한다():
    r = {"shot_plans": {"S1sh1": {"ref_plan": "background"}}}
    assert bsp.normalize_shot_plans(r) == {}


def test_스키마가_곁가지_칸을_더는_통째로_막지_않는다():
    """★막으면 계획 전체가 버려진다 — 막는 일은 코드가 한다."""
    item = bsp.build_share_schema()["properties"]["shot_plans"][
        "additionalProperties"]
    assert item.get("additionalProperties") is not False, (
        "shot_plans 항목이 곁가지 칸을 스키마에서 막으면 계획이 통째로 버려진다")
    # 그래도 share_groups 는 엄격하게 남는다 — 거기 칸은 뜻이 다르다.
    grp = bsp.build_share_schema()["properties"]["share_groups"]["items"]
    assert grp.get("additionalProperties") is False


def test_스키마_위반이_재시도를_건너뛰지_않는다():
    """★★★핵심. 종전에는 예외가 루프 밖으로 새어 한 번 만에 죽었다."""
    from app.modules.llm.safety import SchemaValidationError

    calls: list = []

    def _fake(module, system, content, schema, **kw):
        calls.append(content)
        if len(calls) == 1:
            raise SchemaValidationError("group_key was unexpected")
        return {"share_groups": [{"group_key": "g", "bg_authority": "seed",
                                  "shot_tags": ["S1sh1"],
                                  # evidence 는 **목록**이다 — 손으로 모양을
                                  # 지어내면 프로덕션이 안 받는 것을 시험이
                                  # 통과시킨다.
                                  "evidence": [{"scene_index": 1,
                                                "quote_ko": "원문"}]}],
                "shot_plans": {"S1sh1": {"ref_plan": "background"}}}

    out = bsp.run_background_share_plan(
        scenes_block="S1", shots_block="S1sh1",
        shot_tags=["S1sh1"], scene_texts={1: "원문"},
        call_structured_fn=_fake, max_attempts=3)
    assert out["attempts"] == 2, out
    assert len(calls) == 2, "재시도가 안 돌았다"
    assert "schema:" in calls[1], "위반을 다음 시도에 안 알려 줬다"


def test_상한을_다_쓰면_그대로_선다():
    """양성 확인 — 관대해진 것이 아니라 재시도가 도는 것뿐이다."""
    from app.modules.llm.safety import SchemaValidationError

    def _always_bad(*a, **k):
        raise SchemaValidationError("nope")

    with pytest.raises(SchemaValidationError):
        bsp.run_background_share_plan(
            scenes_block="S1", shots_block="S1sh1",
            shot_tags=["S1sh1"], scene_texts={1: "원문"},
            call_structured_fn=_always_bad, max_attempts=3)
