"""Tests for Phase 9 — forward-facing PassStore registration from dispatch_cli.py
(renamed from test_via_steprunner.py in CP-5 Phase 8).

Validates:
  (a) A successful dispatch registers a PassStore take record with origin='test_dispatch'.
  (b) PassStore write failures are swallowed — the dispatch exit code stays 0.
  (c) All six dispatch paths hit the registration (parametrized).
"""

from __future__ import annotations

import json
import sys
from pathlib import Path

import pytest

_RECOIL_ROOT = Path(__file__).resolve().parents[3]
if str(_RECOIL_ROOT) not in sys.path:
    sys.path.insert(0, str(_RECOIL_ROOT))

from recoil.pipeline.tools import dispatch_cli as T  # type: ignore  # noqa: E402


# ── Fixtures ─────────────────────────────────────────────────────

class _FakeResult:
    def __init__(self, success=True, output_path="/tmp/out.mp4", cost_usd=1.23,
                 shot_id="X", pass_id=None, video_path=None, error=None, api_metadata=None):
        self.success = success
        self.output_path = output_path
        self.cost_usd = cost_usd
        self.shot_id = shot_id
        self.pass_id = pass_id or shot_id
        self.video_path = video_path or output_path
        self.error = error
        self.api_metadata = api_metadata


@pytest.fixture
def tmp_projects(tmp_path, monkeypatch):
    projects = tmp_path / "projects"
    (projects / "driver-beware" / "_pipeline" / "state" / "visual" / "passes").mkdir(parents=True)
    (projects / ".recoil-data-root").touch()  # paths-refactor-v2 sentinel (harness pre-flight infra fix)
    # Redirect projects_root() in core.paths so PassStore writes into tmp
    monkeypatch.setenv("RECOIL_PROJECTS_ROOT", str(projects))
    # Also redirect pass_store's imported projects_root() (bound at import time)
    monkeypatch.setenv("RECOIL_PROJECTS_ROOT", str(projects))
    return projects


# ── (a) Happy path ──────────────────────────────────────────────

def test_register_writes_take_with_origin_test_dispatch(tmp_projects):
    T._register_test_dispatch_with_passstore(
        project="driver-beware",
        original_pass_id="EP001_SH02",
        segment_shot_ids=["EP001_SH02"],
        model="seeddance-2.0",
        prompt="hello world",
        output_path="/tmp/out.mp4",
        cost_usd=1.23,
        latency_seconds=42.0,
        start_frame="/tmp/start.jpg",
        refs=["KIT"],
        duration=5,
        aspect_ratio="16:9",
    )
    state_file = tmp_projects / "driver-beware" / "_pipeline" / "state" / "visual" / "passes" / "ep_001_pass_state.json"
    assert state_file.exists(), "PassStore did not write ep_001_pass_state.json"
    state = json.loads(state_file.read_text())
    passes = state.get("passes", {})
    assert passes, "no passes recorded"
    record = next(iter(passes.values()))
    assert record["takes"], "no take appended"
    take = record["takes"][0]
    assert take["origin"] == "test_dispatch"
    assert take["origin_pass_id"] == "EP001_SH02"
    assert take["model"] == "seeddance-2.0"
    assert take["cost_usd"] == pytest.approx(1.23)
    assert take["output_path"] == "/tmp/out.mp4"
    # `dispatched_by` is a stable record marker — kept as-is so existing PassStore
    # records remain queryable across the CP-5 rename.
    assert take["dispatched_by"] == "test_via_steprunner.py"
    assert take["dispatched_at"].endswith("Z")


def test_register_normalises_non_ep_prefix_ids(tmp_projects):
    """SEEDANCE_I2V_* style IDs should route into ep_001 with a normalised pass_id."""
    T._register_test_dispatch_with_passstore(
        project="driver-beware",
        original_pass_id="SEEDANCE_I2V_1700000000",
        segment_shot_ids=["SEEDANCE_I2V_1700000000"],
        model="seeddance-2.0",
        prompt="p",
        output_path="/tmp/out.mp4",
        cost_usd=0.5,
        latency_seconds=1.0,
    )
    state_file = tmp_projects / "driver-beware" / "_pipeline" / "state" / "visual" / "passes" / "ep_001_pass_state.json"
    assert state_file.exists()
    state = json.loads(state_file.read_text())
    ps_pass_ids = list(state.get("passes", {}).keys())
    assert any(pid.startswith("EP001_TEST_") for pid in ps_pass_ids), ps_pass_ids
    # And the take preserves the original id
    take = next(iter(state["passes"].values()))["takes"][0]
    assert take["origin_pass_id"] == "SEEDANCE_I2V_1700000000"


def test_truncates_prompt_to_500_chars(tmp_projects):
    long_prompt = "x" * 1200
    T._register_test_dispatch_with_passstore(
        project="driver-beware",
        original_pass_id="EP001_SH02",
        segment_shot_ids=["EP001_SH02"],
        model="seeddance-2.0",
        prompt=long_prompt,
        output_path="/tmp/out.mp4",
        cost_usd=0.5,
        latency_seconds=1.0,
    )
    state_file = tmp_projects / "driver-beware" / "_pipeline" / "state" / "visual" / "passes" / "ep_001_pass_state.json"
    take = next(iter(json.loads(state_file.read_text())["passes"].values()))["takes"][0]
    assert len(take["prompt"]) == 500


# ── (b) Failure swallowed ───────────────────────────────────────

def test_passstore_failure_does_not_raise(tmp_projects, monkeypatch, caplog):
    """If PassStore.append_pass_take raises, the helper logs and returns normally."""
    import recoil.execution.pass_store as _ps

    class BoomStore:
        def __init__(self, project): pass
        def get_pass(self, pass_id): return None
        def create_pass(self, pass_id, segment_shot_ids): pass
        def append_pass_take(self, pass_id, take): raise RuntimeError("simulated PassStore failure")
        def update_pass(self, pass_id, **kwargs): pass

    monkeypatch.setattr(_ps, "PassStore", BoomStore)
    # This must not raise
    T._register_test_dispatch_with_passstore(
        project="driver-beware",
        original_pass_id="EP001_SH02",
        segment_shot_ids=["EP001_SH02"],
        model="seeddance-2.0",
        prompt="hi",
        output_path="/tmp/out.mp4",
        cost_usd=0.1,
        latency_seconds=1.0,
    )
    # And should have warned
    assert any("PassStore registration failed" in rec.message for rec in caplog.records), caplog.records


# ── (c) All six dispatch paths invoke the helper ────────────────

@pytest.mark.parametrize("dispatch_path", [
    "kling_o3_elements",
    "seedance_i2v",
    "seedance_r2v",
    "multi_shot_sequence",
    "coverage",
    "standard_single_shot",
])
def test_each_dispatch_path_invokes_passstore_register(dispatch_path):
    """Static-source check: the Phase 9 helper is referenced from every dispatch path."""
    import recoil.pipeline.tools.dispatch_cli as mod
    src = Path(mod.__file__).read_text(encoding="utf-8")
    assert src.count("_register_test_dispatch_with_passstore(") >= 6, (
        f"Expected ≥6 call sites for _register_test_dispatch_with_passstore, "
        f"found {src.count('_register_test_dispatch_with_passstore(')}"
    )
    markers = {
        "kling_o3_elements": "_dispatch_kling_o3_elements",
        "seedance_i2v": "_dispatch_seedance_i2v",
        "seedance_r2v": "_dispatch_seedance_r2v",
        "multi_shot_sequence": "Multi-shot sequence",
        "coverage": "Coverage",
        "standard_single_shot": "Standard or Action single-shot",
    }
    assert markers[dispatch_path] in src, f"marker for {dispatch_path} missing from dispatch_cli.py"
