"""Storyboard landing seam tests."""

from __future__ import annotations

import json
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch

import pytest

from recoil.core.paths import ProjectPaths
from recoil.execution.step_runner import StepRunner
from recoil.execution.step_types import ProjectPaths as ExecutionProjectPaths
from recoil.execution.types import GenerationResult
from recoil.pipeline._lib.sidecar import compute_sha256


PNG_BYTES = b"\x89PNG\r\n\x1a\nstoryboard-test-bytes"


class _Store:
    project = "test"

    def __init__(self) -> None:
        self.updates: list[tuple[str, dict]] = []
        self.takes: list[tuple[str, dict]] = []
        self.acquire_next_take_number_calls = 0

    def update_shot(self, shot_id: str, **kwargs) -> None:
        self.updates.append((shot_id, kwargs))

    def get_shot(self, shot_id: str) -> dict:
        return {"status": "keyframe_generating"}

    def append_take(self, shot_id: str, take_record: dict) -> int:
        self.takes.append((shot_id, take_record))
        return len(self.takes) - 1

    def acquire_next_take_number(self, shot_id: str) -> int:
        self.acquire_next_take_number_calls += 1
        return 3


def _runner(tmp_path: Path) -> tuple[StepRunner, _Store, ExecutionProjectPaths]:
    project_root = tmp_path / "project"
    paths = ExecutionProjectPaths(
        project="test",
        project_root=project_root,
        frames_dir=project_root / "prep" / "ep_001",
        video_dir=project_root / "renders" / "ep_001",
        plans_dir=project_root / "_pipeline" / "shot_plans",
        previs_dir=project_root / "prep" / "ep_001",
    )
    store = _Store()
    return StepRunner(store=store, paths=paths), store, paths


def _run_keyframe(
    runner: StepRunner,
    *,
    save_dir: Path | str | None = None,
    filename_stem: str | None = None,
    sidecar_extra: dict | None = None,
):
    fake_adapter = SimpleNamespace(
        direct_submit_image=lambda unified: {
            "image_bytes": PNG_BYTES,
            "native_id": "test-native-id",
            "operation": None,
            "cost_usd": 0.01,
        }
    )
    with patch(
        "recoil.execution.providers.resolve_adapter",
        return_value=(fake_adapter, "default"),
    ):
        return runner.execute_keyframe(
            shot_id="EP001_SH07",
            prompt="draw a board",
            model="gpt-image-2",
            max_gate_retries=0,
            inputs_snapshot={"inputs_snapshot_hash": "abc123"},
            save_dir=save_dir,
            filename_stem=filename_stem,
            sidecar_extra=sidecar_extra,
        )


def test_episode_storyboards_dir(tmp_path):
    paths = ProjectPaths(project_root=tmp_path / "testproj")

    storyboards_dir = paths.episode_storyboards_dir(7)

    assert storyboards_dir == paths.episode_prep_dir(7) / "storyboards"
    assert storyboards_dir == tmp_path / "testproj" / "prep" / "ep_007" / "storyboards"
    assert not storyboards_dir.exists()


def test_save_keyframe_override(tmp_path):
    runner, store, paths = _runner(tmp_path)
    save_dir = tmp_path / "storyboards"

    result = _run_keyframe(
        runner,
        save_dir=save_dir,
        filename_stem="EP001_CONT_004_v01",
    )

    override_png = save_dir / "EP001_CONT_004_v01.png"
    assert result.success is True
    assert override_png.read_bytes() == PNG_BYTES
    assert (save_dir / "EP001_CONT_004_v01.png.json").is_file()
    assert store.acquire_next_take_number_calls == 0

    default_result = GenerationResult(success=True, image_data=PNG_BYTES)
    default_path = runner._save_keyframe("EP001_SH07", default_result, take_number=3)
    assert default_path == paths.frames_dir / "TEST_EP001_S07_take3.png"
    assert default_path.read_bytes() == PNG_BYTES

    with pytest.raises(ValueError, match="save_dir and filename_stem must be set together"):
        runner._save_keyframe("EP001_SH07", default_result, save_dir=save_dir)
    with pytest.raises(ValueError, match="save_dir and filename_stem must be set together"):
        _run_keyframe(runner, filename_stem="missing_dir")


def test_sidecar_extra_merge(tmp_path, monkeypatch):
    runner, _store, _paths = _runner(tmp_path)
    save_dir = tmp_path / "storyboards"
    writes: list[Path] = []

    from recoil.pipeline._lib import sidecar as sidecar_mod

    real_write_sidecar_dict = sidecar_mod.write_sidecar_dict

    def tracked_write_sidecar_dict(sidecar_path: Path, sidecar_dict: dict) -> None:
        writes.append(sidecar_path)
        real_write_sidecar_dict(sidecar_path, sidecar_dict)

    monkeypatch.setattr(sidecar_mod, "write_sidecar_dict", tracked_write_sidecar_dict)

    _run_keyframe(
        runner,
        save_dir=save_dir,
        filename_stem="EP001_CONT_004_v01",
        sidecar_extra={"kind": "storyboard", "batch_id": "X"},
    )

    png_path = save_dir / "EP001_CONT_004_v01.png"
    sidecar_path = save_dir / "EP001_CONT_004_v01.png.json"
    sidecar = json.loads(sidecar_path.read_text())

    assert sidecar["kind"] == "storyboard"
    assert sidecar["batch_id"] == "X"
    assert sidecar["sha256"] == compute_sha256(png_path)
    assert writes == [sidecar_path]
