"""CP-6 Phase 5 — Standalone integration POC for keyframe→video Workflow.

Proves the Workflow layer composes a realistic two-step DAG end-to-end
against a stubbed StepRunner. NO PRODUCTION CODE IS MODIFIED in this
phase — see SPEC § Phase 5 for the rationale (production_loop.py's
keyframe and video dispatches live in separate methods called from
separate orchestrator-loop iterations; there is no sequential pair to
migrate without refactoring the loop-state machine, which is out of
scope for CP-6).

CP-7+ will land the production-path migration after Take semantics
are designed.
"""
from types import SimpleNamespace

import pytest

from recoil.pipeline.core.dispatch import _reset_bootstrap_for_tests
from recoil.pipeline.core.dispatch_context import DispatchContext
from recoil.pipeline.core.registry import _reset_for_tests
from recoil.pipeline.core.workflow import Workflow, WorkflowStep


@pytest.fixture(autouse=True)
def _reset_dispatch_state():
    """Each POC test gets a clean modality registry + bootstrap memo so
    the per-test _StubStepRunner instance is the one runners delegate to."""
    _reset_for_tests()
    _reset_bootstrap_for_tests()
    yield
    _reset_for_tests()
    _reset_bootstrap_for_tests()


class _StubStepRunner:
    """Mirrors the dispatch-time interface that ImageRunner / VideoRunner
    delegate to. Returns SimpleNamespace shaped like StepResult."""

    def __init__(self, kf_succeeds: bool = True, vid_succeeds: bool = True):
        self.kf_succeeds = kf_succeeds
        self.vid_succeeds = vid_succeeds
        self.kf_calls = 0
        self.vid_calls = 0

    def execute_keyframe(self, **kwargs):
        self.kf_calls += 1
        return SimpleNamespace(
            shot_id=kwargs.get("shot_id", "TEST"),
            success=self.kf_succeeds,
            final_state="keyframe_generated" if self.kf_succeeds else "keyframe_semantic_failed",
            output_path="/tmp/poc.png" if self.kf_succeeds else None,
            cost_usd=0.04,
            error=None if self.kf_succeeds else "stub gate fail",
            take_index=0,
            gate_verdict=None,
            model="nbp",
            pipeline="still",
        )

    def execute_video(self, **kwargs):
        self.vid_calls += 1
        return SimpleNamespace(
            shot_id=kwargs.get("shot_id", "TEST"),
            success=self.vid_succeeds,
            final_state="video_complete" if self.vid_succeeds else "video_failed",
            output_path="/tmp/poc.mp4" if self.vid_succeeds else None,
            cost_usd=0.20,
            error=None if self.vid_succeeds else "stub video fail",
            take_index=0,
            gate_verdict=None,
            model="seeddance-2.0",
            pipeline="i2v",
        )


def _build_kf_video_workflow(shot_id: str = "EP001_SH02") -> Workflow:
    """Construct the canonical keyframe→video Workflow shape that a future
    production migration would use."""
    kf_payload = {"shot_id": shot_id, "prompt": "a quiet hallway", "model": "nbp", "aspect_ratio": "9_16"}
    vid_payload = {
        "shot_id": shot_id,
        "prompt": "a hand reaches toward the lamp",
        "model": "kling-o3",
        "start_frame": "/tmp/poc.png",
        "duration": 5,
        "aspect_ratio": "9_16",
    }
    return Workflow(
        workflow_id=f"poc_{shot_id}_kf_vid",
        steps=[
            WorkflowStep(step_id="keyframe", modality="image_t2i", payload=kf_payload),
            WorkflowStep(
                step_id="video",
                modality="video_i2v",
                payload=vid_payload,
                depends_on=["keyframe"],
            ),
        ],
        global_provenance={"shot_id": shot_id, "scene_id": "ep001_sc02"},
    )


def test_kf_video_poc_happy_path():
    """Both steps succeed; receipts attached; provenance includes workflow_id+step_id."""
    sr = _StubStepRunner()
    ctx = DispatchContext(
        caller_id="cp6_phase5_poc",
        step_runner=sr,
        project="poc_test",
        episode=1,
    )
    wf = _build_kf_video_workflow()
    wf.run(context=ctx)

    assert sr.kf_calls == 1
    assert sr.vid_calls == 1
    kf = wf.get_step("keyframe")
    vid = wf.get_step("video")
    assert kf.status == "succeeded"
    assert vid.status == "succeeded"
    assert kf.receipt.run_result.success is True
    assert vid.receipt.run_result.success is True
    assert kf.receipt.provenance["workflow_id"] == wf.workflow_id
    assert kf.receipt.provenance["workflow_step_id"] == "keyframe"
    assert vid.receipt.provenance["workflow_step_id"] == "video"
    # global_provenance bleeds into each step's receipt
    assert kf.receipt.provenance["shot_id"] == "EP001_SH02"
    assert kf.receipt.provenance["scene_id"] == "ep001_sc02"


def test_kf_video_poc_keyframe_fail_skips_video():
    """Keyframe fails → video skipped (depends_on cascade). Video.execute NOT called."""
    sr = _StubStepRunner(kf_succeeds=False)
    ctx = DispatchContext(
        caller_id="cp6_phase5_poc",
        step_runner=sr,
        project="poc_test",
        episode=1,
    )
    wf = _build_kf_video_workflow()
    wf.run(context=ctx)

    assert sr.kf_calls == 1
    assert sr.vid_calls == 0  # skipped
    kf = wf.get_step("keyframe")
    vid = wf.get_step("video")
    assert kf.status == "failed"
    assert kf.receipt.run_result.success is False
    assert vid.status == "skipped"
    assert vid.receipt is None  # no dispatch happened


def test_kf_video_poc_video_fail_propagates():
    """Keyframe succeeds, video fails. Workflow completes with mixed status."""
    sr = _StubStepRunner(vid_succeeds=False)
    ctx = DispatchContext(
        caller_id="cp6_phase5_poc",
        step_runner=sr,
        project="poc_test",
        episode=1,
    )
    wf = _build_kf_video_workflow()
    wf.run(context=ctx)

    assert sr.kf_calls == 1
    assert sr.vid_calls == 1
    assert wf.get_step("keyframe").status == "succeeded"
    assert wf.get_step("video").status == "failed"
    assert wf.get_step("video").receipt.run_result.error == "stub video fail"


def test_kf_video_poc_round_trip_serialization():
    """Executed workflow round-trips through to_dict / from_dict."""
    sr = _StubStepRunner()
    ctx = DispatchContext(
        caller_id="cp6_phase5_poc",
        step_runner=sr,
        project="poc_test",
        episode=1,
    )
    wf = _build_kf_video_workflow()
    wf.run(context=ctx)

    dumped = wf.to_dict()
    reloaded = Workflow.from_dict(dumped)
    assert reloaded.workflow_id == wf.workflow_id
    assert reloaded.get_step("keyframe").status == "succeeded"
    assert reloaded.get_step("video").status == "succeeded"
    assert reloaded.get_step("keyframe").receipt is not None
    assert reloaded.get_step("video").receipt.run_result.success is True
