"""Char-sheet Brick 1 Phase 3: shelf turn views reach the DISPATCHED r2v refs as
individual paths; the identity-only path is byte-identical (full refs + manifest
snapshot). Bundle angle resolution is pinned to hero-only in the fixture so the
ONLY variable is the shelf turn views."""
from __future__ import annotations
import sys
from pathlib import Path
from PIL import Image

_REPO = Path(__file__).resolve().parents[4]   # recoil/pipeline/_lib/tests -> repo root
if str(_REPO) not in sys.path:
    sys.path.insert(0, str(_REPO))

from recoil.pipeline._lib.dispatch_payload import build_dispatch_payload  # noqa: E402
from recoil.pipeline._lib.plan_loader import CanonicalShot, CharacterEntry  # noqa: E402
from recoil.core.ref_types import RefAsset, ReferenceBundle  # noqa: E402


def _png(p: Path):
    p.parent.mkdir(parents=True, exist_ok=True)
    Image.new("RGB", (8, 8)).save(p)


def _jade_bundle(look: Path, cid: str, *, max_turn_views: int = 3) -> ReferenceBundle:
    subject = cid.lower()
    assets = [
        RefAsset(
            path=look / "jade-identity.png",
            role="identity",
            subject=subject,
            kind="identity",
            is_hero=True,
        )
    ]
    fullbody = look / "jade-fullbody.png"
    if fullbody.is_file():
        assets.append(
            RefAsset(
                path=fullbody,
                role="identity",
                subject=subject,
                kind="fullbody",
            )
        )
    fallback_profile = look / "bundle-fallback-profile.png"
    if fallback_profile.is_file():
        assets.append(
            RefAsset(
                path=fallback_profile,
                role="identity",
                subject=subject,
                kind="identity",
            )
        )
    for view in ("front", "profile", "back", "threequarter")[:max_turn_views]:
        turn = look / "turn" / f"jade-turn-{view}.png"
        if turn.is_file():
            assets.append(
                RefAsset(
                    path=turn,
                    role="identity",
                    subject=subject,
                    kind="turn",
                    view=view,
                )
            )
    return ReferenceBundle(tuple(assets))


def _stage_jade(tmp_path, monkeypatch):
    monkeypatch.setattr("recoil.pipeline._lib.dispatch_payload.projects_root", lambda: tmp_path)
    monkeypatch.setattr("recoil.core.paths.projects_root", lambda: tmp_path)
    monkeypatch.setattr("recoil.pipeline._lib.dispatch_payload._resolve_start_frame",
                        lambda *, shot, project, override: Path("/dev/null"))
    # Pin bundle angle resolution to hero-only so the snapshot is deterministic
    # and the ONLY variable across tests is the shelf turn/fullbody asset set.
    look = tmp_path / "tartarus" / "assets" / "char" / "jade" / "base"
    _png(look / "jade-identity.png")
    monkeypatch.setattr(
        "recoil.pipeline._lib.dispatch_payload.resolve_character_bundle",
        lambda paths, cid, phase=None, max_turn_views=3: _jade_bundle(
            look, cid, max_turn_views=max_turn_views
        ),
    )
    monkeypatch.setattr("recoil.pipeline._lib.dispatch_payload.resolve_location_refs",
                        lambda paths, lid: {})
    from recoil.pipeline._lib import dispatch_payload as _dp
    monkeypatch.setitem(_dp._project_config_cache, "tartarus", {})
    raw = {"shot_id": "EP001_SH06", "scene_index": 1,
           "routing_data": {"target_editorial_duration_s": 3},
           "asset_data": {"location_id": "int_lab",
                          "characters": [{"char_id": "JADE", "wardrobe_phase_id": "p1"}]},
           "compiled_prompts": {"seeddance_t2v": "p"}, "prompt_data": {"shot_type": "MS"}}
    cs = CanonicalShot(shot_id="EP001_SH06", scene_index=1, sequence_id=None, pipeline="r2v",
                       previs_model="gemini-3-pro-image-preview", video_model="seedream-v4.5",
                       location_id="int_lab",
                       characters=[CharacterEntry(char_id="JADE", wardrobe_phase_id="p1")],
                       shot_type="MS", duration_s=3.0, is_env_only=False, has_dialogue=False,
                       aspect_ratio=None, raw=raw)
    return cs, look


def _payload(cs):
    return build_dispatch_payload(shot=cs, project="tartarus", modality="r2v_multi",
                                  batch_shots=[cs], episode="ep_001", dry_run=True)


def test_identity_only_byte_identical(tmp_path, monkeypatch):
    """No shelf turn/fullbody -> resolve_character_bundle returns identity-only ->
    FULL refs + manifest snapshot is exactly the identity hero (bundle pinned to
    hero-only, no location refs). Proves the identity-only path is byte-stable."""
    cs, _look = _stage_jade(tmp_path, monkeypatch)
    p = _payload(cs)
    assert [Path(r).name for r in p.get("reference_images", [])] == ["jade-identity.png"]
    assert p.get("ref_manifest", {}) == {"identity_1": 1}


def test_identity_with_bundle_angle_backfill_preserved(tmp_path, monkeypatch):
    """A successful identity shot with a bundle fallback angle and no shelf views:
    the non-hero bundle asset is preserved in dispatched refs."""
    cs, look = _stage_jade(tmp_path, monkeypatch)
    _png(look / "bundle-fallback-profile.png")
    # No shelf turn/fullbody views -> shelf_view_paths empty -> bundle back-fill path.
    p = _payload(cs)
    names = [Path(r).name for r in p.get("reference_images", [])]
    assert names == ["jade-identity.png", "bundle-fallback-profile.png"]
    assert p.get("ref_manifest", {}).get("identity_1") == 1


def test_shelf_turn_views_reach_dispatched_refs(tmp_path, monkeypatch):
    cs, look = _stage_jade(tmp_path, monkeypatch)
    _png(look / "turn" / "jade-turn-front.png")
    _png(look / "turn" / "jade-turn-profile.png")
    p = _payload(cs)
    names = [Path(r).name for r in p.get("reference_images", [])]
    # identity hero FIRST, then the shelf turn views as INDIVIDUAL paths.
    assert names[0] == "jade-identity.png"
    assert "jade-turn-front.png" in names and "jade-turn-profile.png" in names
    assert len(names) == len(set(names)), "individual refs, no dupes, no composite"
    assert p.get("ref_manifest", {}).get("identity_1") == 1


def test_shelf_fullbody_enriches_without_turn_views(tmp_path, monkeypatch):
    """A shelf fullbody (kind=fullbody, is_hero=False) also triggers the non-identity
    enrichment branch even with NO turn views - included as an individual ref after the
    identity hero, gate still anchored on the identity hero (so this is NOT identity-only
    -> it is deliberately NOT byte-identical)."""
    cs, look = _stage_jade(tmp_path, monkeypatch)
    _png(look / "jade-fullbody.png")
    p = _payload(cs)
    names = [Path(r).name for r in p.get("reference_images", [])]
    assert names[0] == "jade-identity.png"
    assert "jade-fullbody.png" in names
    assert len(names) == len(set(names))


def test_shelf_supersedes_bundle_angle_backfill(tmp_path, monkeypatch):
    """With both bundle fallback angles and shelf turn views, shelf views win -
    exercises the `shelf_view_paths or angle_paths` precedence branch against a
    non-empty bundle fallback list."""
    cs, look = _stage_jade(tmp_path, monkeypatch)
    _png(look / "bundle-fallback-profile.png")
    _png(look / "turn" / "jade-turn-front.png")
    p = _payload(cs)
    names = [Path(r).name for r in p.get("reference_images", [])]
    assert "jade-turn-front.png" in names
    assert "bundle-fallback-profile.png" not in names, "shelf turn views must supersede fallback angles"
