"""Test #4 from BUILD_SPEC EP001-render-rootcause-fix.

CanonicalShot must survive the dataclasses.asdict() → CanonicalShot(**d)
round-trip used by episode_runner._scene_from_batch + _build_workflow_for_beat.
After reconstruction, .characters must be list[CharacterEntry], not list[dict].
"""

from __future__ import annotations

import dataclasses

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


def _make_shot() -> CanonicalShot:
    return CanonicalShot(
        shot_id="EP001_SH02",
        scene_index=1,
        sequence_id=None,
        pipeline="video",
        previs_model="gemini-3.1-flash-image-preview",
        video_model="seeddance-2.0",
        location_id="int_sadie_apartment",
        characters=[
            CharacterEntry(char_id="JADE", wardrobe_phase_id="p1"),
            CharacterEntry(char_id="WREN"),
        ],
        shot_type="MS",
        duration_s=5.0,
        is_env_only=False,
        has_dialogue=False,
        aspect_ratio="9:16",
        raw={},
    )


# ── Test #4 — characters re-typify after asdict round-trip ───────────

def test_characters_retypify_after_asdict():
    """SYNTHESIS §8 test #4.

    Episode runner serializes via dataclasses.asdict and reconstructs
    via CanonicalShot(**d). After reconstruction, .characters must
    contain CharacterEntry instances, not raw dicts.
    """
    shot = _make_shot()
    d = dataclasses.asdict(shot)
    # asdict produces a list[dict] for characters — confirm setup.
    assert isinstance(d["characters"][0], dict)
    assert d["characters"][0]["char_id"] == "JADE"
    assert d["characters"][0]["wardrobe_phase_id"] == "p1"

    # Reconstruct exactly like episode_runner._build_workflow_for_beat.
    reborn = CanonicalShot(**d)
    assert len(reborn.characters) == 2
    assert all(isinstance(c, CharacterEntry) for c in reborn.characters)
    assert reborn.characters[0].char_id == "JADE"
    assert reborn.characters[0].wardrobe_phase_id == "p1"
    assert reborn.characters[1].char_id == "WREN"
    assert reborn.characters[1].wardrobe_phase_id is None


def test_double_round_trip_is_idempotent():
    """Two consecutive asdict → CanonicalShot(**d) cycles produce the
    same characters list — proves __post_init__ is idempotent."""
    shot = _make_shot()
    once = CanonicalShot(**dataclasses.asdict(shot))
    twice = CanonicalShot(**dataclasses.asdict(once))
    assert twice.characters == once.characters
    assert all(isinstance(c, CharacterEntry) for c in twice.characters)


def test_empty_characters_post_init_is_noop():
    """A shot with no characters must not crash __post_init__."""
    shot = CanonicalShot(
        shot_id="EP001_SH50",
        scene_index=1,
        sequence_id=None,
        pipeline="video",
        previs_model=None,
        video_model=None,
        location_id="int_void",
        characters=[],
        shot_type="EWS",
        duration_s=4.0,
        is_env_only=True,
        has_dialogue=False,
        aspect_ratio="9:16",
        raw={},
    )
    reborn = CanonicalShot(**dataclasses.asdict(shot))
    assert reborn.characters == []
