"""CP-5 Phase 2 — VideoRunner hardening tests (post-build-review absorption).

Parallel to `test_image_runner_hardening.py` — same 7 bug classes, applied to
VideoRunner / execute_video. The shared `_failure_metadata` helper is imported
from `_shared` (canonical source) so this suite verifies the video runner uses
the same canonical failure metadata shape.
"""

import math
import pathlib
import sys
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  # noqa: E402

ensure_pipeline_importable()

import pytest  # noqa: E402

from recoil.pipeline.core.registry import (  # noqa: E402
    MODALITY_VIDEO_I2V,
    get_runner,
    register_runner,
)
from recoil.pipeline.core.runners._shared import _failure_metadata_production as _failure_metadata  # noqa: E402
from recoil.pipeline.core.runners.image_runner import _step_result_to_run_result  # noqa: E402
from recoil.pipeline.core.runners.video_runner import VideoRunner  # noqa: E402


def _step_result(**overrides):
    """Build a SimpleNamespace shaped like the real StepResult (video flavor)."""
    base = dict(
        shot_id="X",
        success=True,
        final_state="video_complete",
        output_path="/tmp/x.mp4",
        cost_usd=0.20,
        error=None,
        take_index=0,
        gate_verdict=None,
        model="seeddance-2.0",
        pipeline="i2v",
    )
    base.update(overrides)
    return SimpleNamespace(**base)


# ── Bug 1.1 — Path → str coercion (video flavor) ─────────────────────
def test_adapter_coerces_path_to_str():
    sr = _step_result(output_path=pathlib.Path("/tmp/p.mp4"))
    r = _step_result_to_run_result(sr, "X", MODALITY_VIDEO_I2V)
    assert isinstance(r.output_path, str)
    assert r.output_path == "/tmp/p.mp4"


# ── Bug 1.2 — NaN sanitization ───────────────────────────────────────
def test_adapter_sanitizes_nan_cost():
    sr = _step_result(cost_usd=float("nan"))
    r = _step_result_to_run_result(sr, "X", MODALITY_VIDEO_I2V)
    assert r.metadata["cost_usd"] == 0.0
    assert not (
        isinstance(r.metadata["cost_usd"], float) and math.isnan(r.metadata["cost_usd"])
    )


def test_adapter_handles_none_cost():
    sr = _step_result(cost_usd=None)
    r = _step_result_to_run_result(sr, "X", MODALITY_VIDEO_I2V)
    assert r.metadata["cost_usd"] == 0.0


# ── Bug 2.1 (REJECTED) — extra payload keys dropped by .get() ────────
def test_run_ignores_unknown_payload_keys():
    """VideoRunner uses explicit `payload.get("key")` — extra keys never reach
    execute_video. NO `inspect.signature` filtering introduced.
    """
    seen: dict = {}

    class _SR:
        def execute_video(self, **kw):
            seen.update(kw)
            return _step_result()

    runner = VideoRunner(_SR())
    r = runner.run(
        {
            "shot_id": "X",
            "prompt": "p",
            "model": "kling-o3",
            "aspect_ratio": "9_16",
            "client_id": "should_be_ignored",
            "webhook_url": "should_be_ignored",
            "extra_orch_metadata": {"foo": "bar"},
        }
    )
    assert "client_id" not in seen
    assert "webhook_url" not in seen
    assert "extra_orch_metadata" not in seen
    assert r.success is True


# ── Bug 2.2 — required-key surfacing + metadata always populated ─────
def test_run_missing_required_keys_returns_documented_error():
    runner = VideoRunner(SimpleNamespace(execute_video=lambda **kw: _step_result()))
    r = runner.run({"shot_id": "X"})  # missing prompt and model
    assert r.success is False
    assert "missing required keys" in r.error
    assert r.metadata == _failure_metadata()


# ── Bug 6.1 — exception path metadata populated ──────────────────────
def test_exception_path_populates_metadata():
    class _Boom:
        def execute_video(self, **kw):
            raise ValueError("model unavailable")

    r = VideoRunner(_Boom()).run({"shot_id": "X", "prompt": "p", "model": "kling-o3", "aspect_ratio": "9_16"})
    assert r.success is False
    assert "ValueError" in r.error
    assert "model unavailable" in r.error
    for key in (
        "final_state",
        "cost_usd",
        "gate_verdict",
        "take_index",
        "model",
        "pipeline",
    ):
        assert key in r.metadata
    assert r.metadata["final_state"] == "failed"
    assert r.metadata["cost_usd"] == 0.0


# ── Bug 6.2 — KeyboardInterrupt propagates ───────────────────────────
def test_keyboard_interrupt_propagates():
    class _Interrupt:
        def execute_video(self, **kw):
            raise KeyboardInterrupt()

    with pytest.raises(KeyboardInterrupt):
        VideoRunner(_Interrupt()).run(
            {"shot_id": "X", "prompt": "p", "model": "kling-o3", "aspect_ratio": "9_16"}
        )


# ── Bug 6.3 — success without output coerced to failure ──────────────
def test_success_without_output_path_coerced_to_failure():
    sr = _step_result(success=True, output_path=None)
    r = _step_result_to_run_result(sr, "X", MODALITY_VIDEO_I2V)
    assert r.success is False
    assert "no output_path" in (r.error or "")


def test_success_without_output_path_preserves_existing_error():
    sr = _step_result(success=True, output_path=None, error="encoding failed")
    r = _step_result_to_run_result(sr, "X", MODALITY_VIDEO_I2V)
    assert r.success is False
    assert r.error == "encoding failed"


# ── Bug 3.1 — force=True allows rebinding (video modality) ───────────
def test_register_runner_force_rebinds_video():
    class _A:
        modality = "test_video_x"

        def run(self, p):
            pass

    class _B:
        modality = "test_video_x"

        def run(self, p):
            pass

    register_runner("test_video_x", _A())
    with pytest.raises(KeyError):
        register_runner("test_video_x", _B())  # force=False default

    register_runner("test_video_x", _B(), force=True)
    assert get_runner("test_video_x").__class__.__name__ == "_B"


# ── KeyError UX (Bug 5.1) — bootstrap hint mentions video_i2v path ───
def test_get_runner_keyerror_mentions_bootstrap_for_video():
    with pytest.raises(KeyError) as exc:
        get_runner(MODALITY_VIDEO_I2V)
    msg = str(exc.value)
    assert MODALITY_VIDEO_I2V in msg
    assert "register_default_runners" in msg
