"""Tests #1-3 from BUILD_SPEC EP001-render-rootcause-fix.

Bug A regression coverage: plan_loader must accept list[dict] characters
(EP001 actual shape), list[str] characters (legacy), and reject anything
else with a typed PlanValidationError.
"""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from recoil.pipeline._lib.plan_loader import (
    CanonicalShot,
    CharacterEntry,
    PlanValidationError,
    _canonicalize_shot,
    load_plan,
)


def _plan_shot(characters):
    """Build a minimal raw plan shot dict with the supplied `characters` payload."""
    return {
        "shot_id": "EP001_SH02",
        "scene_index": 1,
        "routing_data": {
            "target_editorial_duration_s": 5.0,
            "is_env_only": False,
            "has_dialogue": False,
        },
        "asset_data": {
            "location_id": "int_sadie_apartment",
            "characters": characters,
        },
        "prompt_data": {"shot_type": "MS"},
        "aspect_ratio": "9:16",
        "model": "gemini-3.1-flash-image-preview",
    }


# ── Test #1 — dict characters with wardrobe_phase_id round-trip ──────

def test_plan_loader_extracts_dict_characters():
    """SYNTHESIS §8 test #1.

    Plan ships [{"char_id": "JADE", "wardrobe_phase_id": "p1"}].
    Loader must produce a CharacterEntry with char_id=='JADE' (uppercased)
    and wardrobe_phase_id=='p1'.
    """
    raw = _plan_shot([
        {"char_id": "jade", "wardrobe_phase_id": "p1"},
    ])
    shot = _canonicalize_shot(raw, sequence_id=None)
    assert isinstance(shot, CanonicalShot)
    assert len(shot.characters) == 1
    entry = shot.characters[0]
    assert isinstance(entry, CharacterEntry)
    assert entry.char_id == "JADE"
    assert entry.wardrobe_phase_id == "p1"
    assert entry.visibility == "in_frame"  # default applied


# ── Test #2 — string characters fall back to bare-id entries ─────────

def test_plan_loader_accepts_string_characters():
    """SYNTHESIS §8 test #2.

    Legacy plans ship ["JADE", "WREN"]. Loader must produce
    CharacterEntry instances with char_id populated and
    wardrobe_phase_id=None.
    """
    raw = _plan_shot(["jade", "wren"])
    shot = _canonicalize_shot(raw, sequence_id=None)
    assert len(shot.characters) == 2
    assert shot.characters[0].char_id == "JADE"
    assert shot.characters[0].wardrobe_phase_id is None
    assert shot.characters[1].char_id == "WREN"
    assert shot.characters[1].wardrobe_phase_id is None


# ── Test #3 — invalid types raise PlanValidationError ────────────────

def test_plan_loader_raises_on_invalid_type():
    """SYNTHESIS §8 test #3.

    Any character entry that's neither a dict nor a string raises
    PlanValidationError (subclass of ValueError).
    """
    raw = _plan_shot([42])
    with pytest.raises(PlanValidationError):
        _canonicalize_shot(raw, sequence_id=None)

    # Subclass of ValueError so existing handlers still work.
    raw_b = _plan_shot([("tuple", "garbage")])
    with pytest.raises(ValueError):
        _canonicalize_shot(raw_b, sequence_id=None)


# ── Bonus — deduplication preserves first-wins ───────────────────────

def test_plan_loader_dedupes_on_char_id():
    """Two entries with the same char_id (case-insensitive) keep only the first."""
    raw = _plan_shot([
        {"char_id": "JADE", "wardrobe_phase_id": "p1"},
        {"char_id": "jade", "wardrobe_phase_id": "p2"},
    ])
    shot = _canonicalize_shot(raw, sequence_id=None)
    assert len(shot.characters) == 1
    assert shot.characters[0].wardrobe_phase_id == "p1"


# ── Bonus — empty characters list yields empty CharacterEntry list ───

def test_plan_loader_empty_characters_list():
    raw = _plan_shot([])
    shot = _canonicalize_shot(raw, sequence_id=None)
    assert shot.characters == []


# ── End-to-end — load_plan reads a flat-shots JSON ───────────────────

def test_load_plan_flat_shots_e2e(tmp_path: Path):
    plan_dict = {
        "episode_id": "EP001",
        "project": "tartarus",
        "shots": [
            _plan_shot([{"char_id": "JADE", "wardrobe_phase_id": "p1"}]),
            _plan_shot([{"char_id": "WREN"}]),
        ],
    }
    plan_dict["shots"][1]["shot_id"] = "EP001_SH03"
    plan_path = tmp_path / "plan.json"
    plan_path.write_text(json.dumps(plan_dict))
    plan = load_plan(plan_path)
    assert len(plan.shots) == 2
    assert plan.shots[0].characters[0].char_id == "JADE"
    assert plan.shots[1].characters[0].char_id == "WREN"
