"""CP-7 Phase 6 — end-to-end Take/Beat scenarios under real dispatch.

Exercises the full chain: Take.execute → Workflow.run → dispatch() →
registry → runner.run() → StepResult → RunResult → GenerationReceipt.
StepRunner is stubbed (no live API calls); everything else is live.
"""

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  # noqa: E402
ensure_pipeline_importable()

import pytest  # noqa: E402

from recoil.pipeline.core.dispatch import _reset_bootstrap_for_tests  # noqa: E402
from recoil.pipeline.core.dispatch_context import DispatchContext  # noqa: E402
from recoil.pipeline.core.registry import _reset_for_tests  # noqa: E402
from recoil.pipeline.core.take import Beat, Take  # noqa: E402
from recoil.pipeline.core.workflow import Workflow, WorkflowStep  # noqa: E402


def _step_result(success=True, **overrides):
    base = dict(
        shot_id="X", success=success,
        final_state="keyframe_generated" if success else "failed",
        output_path="/tmp/x.png" if success else None,
        cost_usd=0.04, error=None if success else "boom",
        take_index=0, gate_verdict=None, model="nbp", pipeline="still",
    )
    base.update(overrides)
    return SimpleNamespace(**base)


class _StubStepRunner:
    def __init__(self, *, kf_succeeds=True, vid_succeeds=True):
        self._dispatch_path = "unknown"
        self.kf_succeeds = kf_succeeds
        self.vid_succeeds = vid_succeeds
        self.calls = []

    def execute_keyframe(self, **kw):
        self.calls.append(("keyframe", dict(kw)))
        return _step_result(success=self.kf_succeeds)

    def execute_video(self, **kw):
        self.calls.append(("video", dict(kw)))
        return _step_result(
            success=self.vid_succeeds,
            output_path="/tmp/v.mp4" if self.vid_succeeds else None,
            final_state="video_complete" if self.vid_succeeds else "failed",
            cost_usd=0.20, model="seeddance-2.0", pipeline="i2v",
            error=None if self.vid_succeeds else "video gen failed",
        )


@pytest.fixture(autouse=True)
def reset():
    _reset_for_tests(); _reset_bootstrap_for_tests()
    yield
    _reset_for_tests(); _reset_bootstrap_for_tests()


def _two_step_wf(workflow_id="wf1") -> Workflow:
    return Workflow(workflow_id=workflow_id, steps=[
        WorkflowStep(step_id="kf", modality="image_t2i",
                     payload={"shot_id": "X", "prompt": "p", "model": "nbp", "aspect_ratio": "9_16"}),
        WorkflowStep(step_id="vid", modality="video_i2v",
                     payload={"shot_id": "X", "prompt": "p", "model": "seeddance-2.0", "aspect_ratio": "9_16"},
                     depends_on=["kf"]),
    ])


def test_take_execute_real_dispatch_succeeded():
    sr = _StubStepRunner()
    ctx = DispatchContext(caller_id="cp7_e2e", step_runner=sr,
                          receipts_log_path="DISABLED")
    take = Take(take_id="t0", take_index=0, workflow=_two_step_wf())
    take.execute(context=ctx)
    assert take.status == "succeeded"
    assert all(s.status == "succeeded" for s in take.workflow.steps)
    # Workflow stamping carries through to receipts on each step
    assert take.workflow.steps[0].receipt is not None
    assert take.workflow.steps[0].receipt.provenance["workflow_id"] == "wf1"
    assert take.workflow.steps[0].receipt.provenance["workflow_step_id"] == "kf"


def test_take_execute_real_dispatch_partial():
    sr = _StubStepRunner(vid_succeeds=False)
    ctx = DispatchContext(caller_id="cp7_e2e", step_runner=sr,
                          receipts_log_path="DISABLED")
    take = Take(take_id="t0", take_index=0, workflow=_two_step_wf())
    take.execute(context=ctx)
    assert take.status == "partial"
    assert take.workflow.steps[0].status == "succeeded"
    assert take.workflow.steps[1].status == "failed"


def test_take_execute_real_dispatch_failed_first_step():
    sr = _StubStepRunner(kf_succeeds=False)
    ctx = DispatchContext(caller_id="cp7_e2e", step_runner=sr,
                          receipts_log_path="DISABLED")
    take = Take(take_id="t0", take_index=0, workflow=_two_step_wf())
    take.execute(context=ctx)
    # kf failed → vid skipped → no step succeeded → take.status = "failed"
    assert take.status == "failed"
    assert take.workflow.steps[0].status == "failed"
    assert take.workflow.steps[1].status == "skipped"


def test_take_hooks_pass_through_real_dispatch():
    sr = _StubStepRunner()
    ctx = DispatchContext(caller_id="cp7_e2e", step_runner=sr,
                          receipts_log_path="DISABLED")
    take = Take(take_id="t0", take_index=0, workflow=_two_step_wf())
    log = []
    take.execute(
        context=ctx,
        pre_step=lambda s, w: log.append(("pre", s.step_id)),
        post_step=lambda s, w: log.append(("post", s.step_id, s.status)),
    )
    assert log == [
        ("pre", "kf"), ("post", "kf", "succeeded"),
        ("pre", "vid"), ("post", "vid", "succeeded"),
    ]


def test_beat_multiple_takes_real_dispatch():
    """Multi-take Beat: first attempt fails (kf), second succeeds. First-success
    selects the second."""
    beat = Beat(beat_id="EP001_SH02")

    ctx_fail = DispatchContext(caller_id="cp7_e2e",
                               step_runner=_StubStepRunner(kf_succeeds=False),
                               receipts_log_path="DISABLED")
    take_a = beat.new_take(workflow=_two_step_wf("wf0"))
    take_a.execute(context=ctx_fail)
    assert take_a.status == "failed"

    # Reset registry/bootstrap so the second StepRunner gets wired (CP-6 handoff §5; CP-7 Phase 5).
    _reset_for_tests(); _reset_bootstrap_for_tests()

    ctx_ok = DispatchContext(caller_id="cp7_e2e", step_runner=_StubStepRunner(),
                             receipts_log_path="DISABLED")
    take_b = beat.new_take(workflow=_two_step_wf("wf1"))
    take_b.execute(context=ctx_ok)
    assert take_b.status == "succeeded"

    chosen = beat.select_primary()
    assert chosen == take_b.take_id
    assert beat.primary_take is take_b


def test_beat_round_trip_after_real_dispatch():
    """Beat with multiple executed takes survives to_dict/from_dict including
    receipts on every workflow step."""
    sr = _StubStepRunner()
    ctx = DispatchContext(caller_id="cp7_e2e", step_runner=sr,
                          receipts_log_path="DISABLED")
    beat = Beat(beat_id="EP001_SH02")
    take_0 = beat.new_take(workflow=_two_step_wf("wf0"))
    take_0.execute(context=ctx)
    take_1 = beat.new_take(workflow=_two_step_wf("wf1"))
    take_1.execute(context=ctx)
    beat.select_primary()

    reloaded = Beat.from_dict(beat.to_dict())
    assert reloaded.beat_id == "EP001_SH02"
    assert len(reloaded.takes) == 2
    assert reloaded.takes[0].status == "succeeded"
    assert reloaded.takes[1].status == "succeeded"
    assert reloaded.primary_take_id == take_0.take_id
    # Each step's receipt round-tripped
    for take in reloaded.takes:
        for step in take.workflow.steps:
            assert step.receipt is not None
            assert step.receipt.run_result.success is True


def test_take_public_surface_importable():
    from recoil.pipeline.core import Take, Beat, Scene  # noqa: F401
