"""CP-4 Phase 3 — ImageRunner unit tests."""

import sys
import pathlib
from types import SimpleNamespace

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent.parent.parent))
from recoil.core.paths import ensure_pipeline_importable

ensure_pipeline_importable()


from recoil.pipeline.core.registry import (  # noqa: E402
    MODALITY_IMAGE_T2I,
    RunResult,
    register_runner,
    get_runner,
)
from recoil.pipeline.core.runners.image_runner import ImageRunner  # noqa: E402


class _FakeStepRunner:
    """Captures execute_keyframe calls for assertion.

    Returns a SimpleNamespace shaped like the REAL StepResult contract from
    recoil/execution/step_types.py (verified 2026-04-27). Field names MUST
    match the dataclass exactly — using wrong names here makes the adapter
    tests pass against a fake that doesn't match production behavior.
    """

    def __init__(
        self,
        simulate_success=True,
        simulate_state="keyframe_generated",
        simulate_path="/tmp/fake.png",
    ):
        self.calls = []
        self._success = simulate_success
        self._state = simulate_state
        self._path = simulate_path

    def execute_keyframe(self, **kwargs):
        self.calls.append(kwargs)
        return SimpleNamespace(
            shot_id=kwargs.get("shot_id", "TEST"),
            success=self._success,
            final_state=self._state,
            output_path=self._path,
            cost_usd=0.04,
            error=None,
            take_index=0,
            gate_verdict=None,
            model="nbp",
            pipeline="still",
        )


def test_image_runner_modality_constant():
    sr = _FakeStepRunner()
    r = ImageRunner(sr)
    assert r.modality == MODALITY_IMAGE_T2I == "image_t2i"


def test_image_runner_happy_path():
    sr = _FakeStepRunner()
    r = ImageRunner(sr)
    out = r.run(
        {
            "shot_id": "EP001_SH02",
            "prompt": "a quiet hallway",
            "model": "nbp",
            "aspect_ratio": "9:16",
        }
    )
    assert isinstance(out, RunResult)
    assert out.modality == "image_t2i"
    assert out.success is True
    assert out.output_path == "/tmp/fake.png"
    assert out.metadata["cost_usd"] == 0.04
    assert out.metadata["final_state"] == "keyframe_generated"
    assert "EP001_SH02" in out.id

    # The shim threaded all payload fields through
    assert sr.calls[0]["shot_id"] == "EP001_SH02"
    assert sr.calls[0]["prompt"] == "a quiet hallway"
    assert sr.calls[0]["model"] == "nbp"
    assert sr.calls[0]["aspect_ratio"] == "9:16"
    assert sr.calls[0]["max_gate_retries"] == 3  # default


def test_image_runner_threads_optional_refs():
    sr = _FakeStepRunner()
    r = ImageRunner(sr)
    r.run(
        {
            "shot_id": "EP001_SH02",
            "prompt": "x",
            "model": "nbp",
            "aspect_ratio": "9_16",
            "scene_ref_path": "/tmp/scene.png",
            "pose_ref_path": "/tmp/pose.png",
            "identity_refs": ["/tmp/id1.png", "/tmp/id2.png"],
            "expression_refs": ["/tmp/exp.png"],
            "max_gate_retries": 5,
        }
    )
    call = sr.calls[0]
    from pathlib import Path

    assert call["scene_ref_path"] == Path("/tmp/scene.png")
    assert call["pose_ref_path"] == Path("/tmp/pose.png")
    assert call["identity_refs"] == [Path("/tmp/id1.png"), Path("/tmp/id2.png")]
    assert call["expression_refs"] == [Path("/tmp/exp.png")]
    assert call["max_gate_retries"] == 5


def test_image_runner_missing_required_returns_error_runresult():
    sr = _FakeStepRunner()
    r = ImageRunner(sr)
    out = r.run({"shot_id": "x"})  # missing prompt, model
    assert out.success is False
    assert "missing required keys" in out.error
    # The fake was NOT called
    assert sr.calls == []
    # Bug 6.1 — failure-path metadata: canonical 6 keys, final_state="failed", cost_usd=0.0
    for key in (
        "final_state",
        "cost_usd",
        "gate_verdict",
        "take_index",
        "model",
        "pipeline",
    ):
        assert key in out.metadata, f"failure-path metadata missing key {key!r}"
    assert out.metadata["final_state"] == "failed"
    assert out.metadata["cost_usd"] == 0.0
    assert out.metadata["gate_verdict"] is None
    assert out.metadata["take_index"] is None
    assert out.metadata["model"] is None
    assert out.metadata["pipeline"] is None


def test_image_runner_failed_status_marks_runresult_failed():
    # StepResult.success is the authoritative success bit; final_state is
    # informational only. Set success=False to simulate a failed generation.
    sr = _FakeStepRunner(
        simulate_success=False, simulate_state="keyframe_semantic_failed"
    )
    r = ImageRunner(sr)
    out = r.run({"shot_id": "x", "prompt": "y", "model": "nbp", "aspect_ratio": "9_16"})
    assert out.success is False
    # The fake leaves error=None; success=False alone is the contract assertion.
    # Real StepRunner sets error when success=False; that path is covered by the
    # exception test below.


def test_image_runner_exception_caught_into_runresult():
    class _BoomStepRunner:
        def execute_keyframe(self, **kwargs):
            raise RuntimeError("upstream API down")

    r = ImageRunner(_BoomStepRunner())
    out = r.run({"shot_id": "x", "prompt": "y", "model": "nbp", "aspect_ratio": "9_16"})
    assert out.success is False
    assert "RuntimeError" in out.error
    assert "upstream API down" in out.error
    # Bug 6.1 — exception-path metadata: canonical 6 keys, final_state="failed", cost_usd=0.0
    for key in (
        "final_state",
        "cost_usd",
        "gate_verdict",
        "take_index",
        "model",
        "pipeline",
    ):
        assert key in out.metadata
    assert out.metadata["final_state"] == "failed"
    assert out.metadata["cost_usd"] == 0.0
    assert out.metadata["gate_verdict"] is None
    assert out.metadata["take_index"] is None
    assert out.metadata["model"] is None
    assert out.metadata["pipeline"] is None


def test_image_runner_register_and_resolve():
    sr = _FakeStepRunner()
    runner = ImageRunner(sr)
    register_runner(MODALITY_IMAGE_T2I, runner)
    resolved = get_runner("image_t2i")
    assert resolved is runner
