"""CP-9 Phase 3 — EvalContext dataclass.

Validates Phase-3 brief-locked field shape (target_artifact_path,
target_take, prompt, rubric, judge_id, metadata, scene_takes), Path
coercion, rubric/judge_id non-empty guards, and reserved scene_takes
default-empty.
"""

import sys
import pathlib
from pathlib import Path

import pytest

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


def test_eval_context_construction_happy_path_image(tmp_path: Path) -> None:
    img = tmp_path / "frame.png"
    img.write_bytes(b"\x89PNG\r\n\x1a\n")
    ctx = EvalContext(
        target_artifact_path=img,
        target_take=None,
        prompt="A wide shot of a desert highway at dusk.",
        rubric="Score 0..1 on composition.",
        judge_id="j1",
    )
    assert ctx.target_artifact_path == img
    assert ctx.prompt.startswith("A wide")
    assert ctx.rubric.startswith("Score")
    assert ctx.judge_id == "j1"
    assert ctx.metadata == {}
    assert ctx.scene_takes == []


def test_eval_context_construction_video(tmp_path: Path) -> None:
    vid = tmp_path / "shot.mp4"
    vid.write_bytes(b"ftypisom")
    ctx = EvalContext(
        target_artifact_path=vid,
        target_take=None,
        prompt="Cars passing by",
        rubric="Score motion plausibility 0..1",
        judge_id="j_video",
    )
    assert ctx.target_artifact_path.suffix == ".mp4"


def test_eval_context_construction_audio(tmp_path: Path) -> None:
    aud = tmp_path / "vo.mp3"
    aud.write_bytes(b"ID3")
    ctx = EvalContext(
        target_artifact_path=aud,
        target_take=None,
        prompt="Hold the line",
        rubric="Score voice match 0..1",
        judge_id="j_audio",
    )
    assert ctx.target_artifact_path.name == "vo.mp3"


def test_eval_context_empty_rubric_raises() -> None:
    with pytest.raises(ValueError, match="non-empty"):
        EvalContext(
            target_artifact_path=Path("/tmp/x.png"),
            target_take=None,
            prompt="p",
            rubric="",
            judge_id="j",
        )


def test_eval_context_empty_judge_id_raises() -> None:
    with pytest.raises(ValueError, match="non-empty"):
        EvalContext(
            target_artifact_path=Path("/tmp/x.png"),
            target_take=None,
            prompt="p",
            rubric="rubric here",
            judge_id="",
        )


def test_eval_context_path_string_coerced_to_Path(tmp_path: Path) -> None:
    img = tmp_path / "frame.png"
    img.write_bytes(b"\x89PNG\r\n")
    ctx = EvalContext(
        target_artifact_path=str(img),  # type: ignore[arg-type]
        target_take=None,
        prompt="p",
        rubric="r",
        judge_id="j",
    )
    assert isinstance(ctx.target_artifact_path, Path)
    assert ctx.target_artifact_path == img


def test_eval_context_scene_takes_default_empty_and_reserved() -> None:
    """scene_takes is RESERVED per SYNTHESIS Locked Decision #10. Must
    exist as a field with default-empty list. CP-9 judges must not consume
    it — but the field is documented so future CPs can populate."""
    ctx = EvalContext(
        target_artifact_path=Path("/tmp/x.png"),
        target_take=None,
        prompt="p",
        rubric="r",
        judge_id="j",
    )
    assert ctx.scene_takes == []
    # And mutable — caller may append for future cross-take judges.
    ctx.scene_takes.append("placeholder")
    assert ctx.scene_takes == ["placeholder"]


def test_eval_context_metadata_default_empty_and_unique_per_instance() -> None:
    a = EvalContext(
        target_artifact_path=Path("/tmp/a.png"),
        target_take=None, prompt="p", rubric="r", judge_id="ja",
    )
    b = EvalContext(
        target_artifact_path=Path("/tmp/b.png"),
        target_take=None, prompt="p", rubric="r", judge_id="jb",
    )
    a.metadata["k"] = "v"
    assert "k" not in b.metadata


def test_eval_context_metadata_non_dict_raises() -> None:
    with pytest.raises(TypeError, match="dict"):
        EvalContext(
            target_artifact_path=Path("/tmp/x.png"),
            target_take=None,
            prompt="p",
            rubric="r",
            judge_id="j",
            metadata=["not-a-dict"],  # type: ignore[arg-type]
        )


def test_eval_context_prompt_non_string_raises() -> None:
    with pytest.raises(TypeError, match="string"):
        EvalContext(
            target_artifact_path=Path("/tmp/x.png"),
            target_take=None,
            prompt=42,  # type: ignore[arg-type]
            rubric="r",
            judge_id="j",
        )


def test_eval_context_target_take_accepts_none() -> None:
    """target_take is Any-typed; None is the CP-9 placeholder until
    Phase 4 runners pass through the actual Take."""
    ctx = EvalContext(
        target_artifact_path=Path("/tmp/x.png"),
        target_take=None,
        prompt="p", rubric="r", judge_id="j",
    )
    assert ctx.target_take is None


def test_eval_context_target_take_accepts_arbitrary_object() -> None:
    sentinel = object()
    ctx = EvalContext(
        target_artifact_path=Path("/tmp/x.png"),
        target_take=sentinel,
        prompt="p", rubric="r", judge_id="j",
    )
    assert ctx.target_take is sentinel
