"""Phase 6 integration helpers — shared scaffolding for eval-hook integration tests.

Each helper is intentionally minimal; the integration tests exercise REAL
production code paths (registered runners, dispatch, Workflow.run, Take.execute,
attach_eval_hooks) and only mock at the EvalNode boundary so no live Gemini API
calls fire.

Module-private (prefixed with `_`) — Phase 6 only.
"""

from __future__ import annotations

import sys
import pathlib
from types import SimpleNamespace
from typing import Optional

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()

from recoil.pipeline.core.eval import EvalContext, EvalResult  # noqa: E402


def step_result(success: bool = True, **overrides):
    """Build a SimpleNamespace mimicking StepResult — the shape ImageRunner /
    VideoRunner consume from StepRunner.execute_*.

    Defaults match the keys ImageRunner._step_result_to_run_result reads.
    """
    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:
    """Minimal StepRunner — supports execute_keyframe / execute_video.

    Toggleable per-modality success so tests can force a step to fail.
    Records every call for assertion convenience.
    """

    def __init__(
        self,
        *,
        kf_succeeds: bool = True,
        vid_succeeds: bool = True,
        kf_output_path: str = "/tmp/x.png",
        vid_output_path: str = "/tmp/v.mp4",
    ) -> None:
        self._dispatch_path = "unknown"
        self.kf_succeeds = kf_succeeds
        self.vid_succeeds = vid_succeeds
        self.kf_output_path = kf_output_path
        self.vid_output_path = vid_output_path
        self.calls: list[tuple[str, dict]] = []

    def execute_keyframe(self, **kw):
        self.calls.append(("keyframe", dict(kw)))
        return step_result(
            success=self.kf_succeeds,
            output_path=self.kf_output_path if self.kf_succeeds else None,
        )

    def execute_video(self, **kw):
        self.calls.append(("video", dict(kw)))
        return step_result(
            success=self.vid_succeeds,
            output_path=self.vid_output_path 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",
        )


class FakeEvalNode:
    """Test EvalNode — returns a deterministic EvalResult; tracks calls.

    Satisfies the runtime-checkable EvalNode Protocol (judge_id / model_used /
    evaluate). Use to register against eval modality runners (or directly to a
    PanelOfJudges) so no live Gemini API calls fire.
    """

    def __init__(
        self,
        judge_id: str,
        *,
        score: float = 0.85,
        reasoning: str = "fake reasoning",
        cost_usd: float = 0.001,
        model_used: str = "fake-judge",
        raise_exc: Optional[Exception] = None,
    ) -> None:
        self.judge_id = judge_id
        self.model_used = model_used
        self._score = score
        self._reasoning = reasoning
        self._cost_usd = cost_usd
        self._raise_exc = raise_exc
        self.calls: list[EvalContext] = []

    def evaluate(self, context: EvalContext) -> EvalResult:
        self.calls.append(context)
        if self._raise_exc:
            raise self._raise_exc
        return EvalResult(
            score=self._score,
            reasoning=self._reasoning,
            judge_id=self.judge_id,
            model_used=self.model_used,
            cost_usd=self._cost_usd,
        )
