"""Shared fixtures for pipeline tests."""

import json
import sys
from pathlib import Path

import pytest

# Ensure pipeline root is on path for `from lib.x import ...` style.
# Insert RECOIL_ROOT FIRST so `from core.x import ...` resolves to recoil/core/
# (CP-4 introduced pipeline/core/ which would otherwise shadow it).
PIPELINE_ROOT = Path(__file__).resolve().parent.parent  # recoil/pipeline/
RECOIL_ROOT = PIPELINE_ROOT.parent  # recoil/
sys.path.insert(0, str(PIPELINE_ROOT))
sys.path.insert(0, str(RECOIL_ROOT))

# CP-2 Phase 8 (2026-04-26) — eagerly import execution.assembler at
# conftest time so its module-scope `from google.genai import types as
# genai_types` line binds to the REAL types module before
# test_generate_previs.py's module-scope stub overwrites
# sys.modules["google.genai.types"] with a MagicMock. Pre-Phase 8 this
# was masked by execution/api_client.py being imported via the old
# pipeline/tests/test_api_client.py (which contained
# `from recoil.execution.api_client import SeedDanceClient` at module top).
# The CP-2 stub api_client + the skip-marker on that test means the
# eager assembler load no longer happens incidentally; we restore it
# here. If google-genai is not installed (dry-run test env), assembler
# falls back to genai_types=None and the affected tests use mocks.
try:
    import recoil.execution.assembler  # noqa: F401
except ImportError:
    pass

FIXTURES_DIR = Path(__file__).parent / "fixtures"


@pytest.fixture(autouse=True)
def _reset_dispatch_registry_per_test():
    """Reset the dispatch registry + bootstrap memo before/after each test.

    Post-CP-5 callers (production_loop, pipeline_orchestrator, run_shot) route
    generation through `pipeline.core.dispatch.dispatch`, which lazily
    bootstraps ImageRunner / VideoRunner against the FIRST step_runner it
    sees and caches by id(). Tests in this suite construct fresh MagicMock
    step_runners per case; without an explicit reset the second test's mock
    is silently ignored and the call lands on the first test's now-stale
    runner. Reset the registry before and after every test to keep the
    behavior byte-identical to the pre-migration mock-StepRunner flow.

    Best-effort: if the dispatch helpers aren't importable (older branch,
    optional dep missing) the fixture is a no-op.
    """
    try:
        from recoil.pipeline.core.dispatch import _reset_bootstrap_for_tests
        from recoil.pipeline.core.registry import _reset_for_tests
    except Exception:
        yield
        return
    _reset_for_tests()
    _reset_bootstrap_for_tests()
    yield
    _reset_for_tests()
    _reset_bootstrap_for_tests()


@pytest.fixture
def sample_shot():
    """A single shot dict (MS, one character)."""
    return json.loads((FIXTURES_DIR / "sample_shot.json").read_text())


@pytest.fixture
def sample_storyboard():
    """A minimal storyboard with 2 scenes, 5 shots."""
    return json.loads((FIXTURES_DIR / "sample_storyboard.json").read_text())


@pytest.fixture
def env_shot():
    """An ENV-only shot (no characters)."""
    return {
        "id": 1,
        "name": "corridor_dolly",
        "shot_type": "ENV",
        "action": "Camera pushes through corridor.",
        "characters_in_shot": [],
        "emotion": "",
        "location": "corridor_a",
    }


@pytest.fixture
def complex_shot():
    """A two-character shot (complex tier)."""
    return {
        "id": 4,
        "name": "jinx_ava_argue",
        "shot_type": "TWO_SHOT",
        "action": "Jinx and Ava argue.",
        "characters_in_shot": ["jinx", "ava"],
        "emotion": "tense",
        "dialogue": "We can't go deeper.",
        "location": "bridge",
    }


@pytest.fixture
def wide_shot():
    """A wide shot (simple tier)."""
    return {
        "id": 10,
        "name": "ship_exterior",
        "shot_type": "WIDE",
        "action": "The submarine descends into the trench.",
        "characters_in_shot": ["jinx"],
        "emotion": "",
        "location": "exterior",
    }


@pytest.fixture
def ecu_emotion_shot():
    """ECU with emotion (complex tier)."""
    return {
        "id": 11,
        "name": "jinx_fear",
        "shot_type": "ECU",
        "action": "Extreme close-up of Jinx's eyes widening in fear.",
        "characters_in_shot": ["jinx"],
        "emotion": "fear",
        "location": "corridor_a",
    }


@pytest.fixture
def i2v_shot():
    """Shot with start+end frame (I2V pipeline)."""
    return {
        "id": 20,
        "name": "jinx_turns",
        "shot_type": "MS",
        "action": "Jinx turns to face the noise.",
        "characters_in_shot": ["jinx"],
        "emotion": "startled",
        "start_frame": "/path/to/start.png",
        "end_frame": "/path/to/end.png",
        "location": "corridor_a",
    }


@pytest.fixture
def dialogue_shot():
    """Shot with dialogue (SeedDance pipeline)."""
    return {
        "id": 21,
        "name": "ava_warns",
        "shot_type": "MS",
        "action": "Ava warns about hull breach.",
        "characters_in_shot": ["ava"],
        "emotion": "urgent",
        "dialogue": "Hull integrity at 40 percent!",
        "location": "bridge",
    }


@pytest.fixture
def batch_eligible_scene():
    """A scene with 4 shots eligible for multi-shot batching."""
    return [
        {
            "id": 30, "name": f"shot_{i}", "shot_type": "MS",
            "action": f"Action {i}", "characters_in_shot": ["jinx"],
            "emotion": "", "location": "corridor_a",
        }
        for i in range(30, 34)
    ]


@pytest.fixture
def mock_genai_response():
    """Mock Google genai response with image data."""
    return json.loads((FIXTURES_DIR / "gemini_response.json").read_text())
