"""Tests for the critic structured_output adapters.

Each adapter must either:
1. Run the real CriticLoop subclass and translate the result to a FailureMode dict
2. Raise NotImplementedError if called without the inputs needed to run the class

A stub that returns FailureMode.NONE regardless of input is the bug.
"""

import pytest
from recoil.pipeline._lib.critics import FailureMode


def test_keyframe_rewrite_critic_raises_without_inputs():
    """Calling without artifact + bible + shot must raise, not stub-pass."""
    from recoil.pipeline._lib.critics.keyframe_rewrite_critic import (
        keyframe_rewrite_critic,
    )

    with pytest.raises((NotImplementedError, ValueError)):
        keyframe_rewrite_critic(structured_output=True)


def test_keyframe_rewrite_critic_runs_real_class_when_wired():
    """Adapter calls KeyframeRewriteCritic.run() and translates Dimension list.

    NOTE: KeyframeRewriteCritic.auto_fix() at line 211-216 will silently strip
    VFX blacklist matches via regex on the first retry, leaving a clean prompt
    that PASSES on the second eval. To exercise the FAIL → translation path,
    we patch auto_fix to be a no-op so the failure persists. (Opus Finding 6,
    2026-04-09 review.)
    """
    from unittest.mock import patch
    from recoil.pipeline._lib.critics.keyframe_rewrite_critic import (
        keyframe_rewrite_critic,
        KeyframeRewriteCritic,
    )

    bible = {"characters": {"alice": {"display_name": "Alice"}}}
    shot = {
        "asset_data": {"characters": [{"char_id": "alice"}]},
        "shot_id": "EP001_SH01",
    }

    # Patch auto_fix to a no-op so the VFX blacklist failure persists across attempts
    with patch.object(
        KeyframeRewriteCritic,
        "auto_fix",
        side_effect=lambda artifact, failed_dims, context: artifact,
    ):
        result = keyframe_rewrite_critic(
            structured_output=True,
            artifact="Alice walks into the room with a face melt effect",
            bible=bible,
            shot=shot,
        )
    assert result["passed"] is False
    assert result["failure_mode"] == FailureMode.ANATOMY_FACE_MERGE.value
    assert "VFX" in result["evidence"] or "vfx" in result["evidence"].lower()


def test_keyframe_rewrite_critic_passes_clean_prompt():
    """A clean prompt with character in first 30 words must PASS."""
    from recoil.pipeline._lib.critics.keyframe_rewrite_critic import (
        keyframe_rewrite_critic,
    )

    bible = {"characters": {"alice": {"display_name": "Alice"}}}
    shot = {
        "asset_data": {"characters": [{"char_id": "alice"}]},
        "shot_id": "EP001_SH01",
    }

    result = keyframe_rewrite_critic(
        structured_output=True,
        artifact="Alice strides confidently through the warehouse, her boots echoing on the concrete floor",
        bible=bible,
        shot=shot,
    )
    assert result["passed"] is True
    assert result["failure_mode"] == FailureMode.NONE.value


# test_video_enhancement_critic_translates_safety_filter RETIRED 2026-06-09
# with video_enhancement_critic.py (enrichment superseded by prose_author).


def test_all_adapters_no_longer_silently_pass_with_inputs_none():
    """Every adapter must raise NotImplementedError or ValueError when called with no real inputs."""
    from recoil.pipeline._lib.critics import (
        keyframe_rewrite_critic,
        plan_pass_critic,
        ref_image_critic,
        start_frame_critic,
        turnaround_critic,
        video_frame_critic,
    )

    adapters = [
        ("keyframe_rewrite", keyframe_rewrite_critic.keyframe_rewrite_critic),
        ("plan_pass", plan_pass_critic.plan_pass_critic),
        ("ref_image", ref_image_critic.ref_image_critic),
        ("start_frame", start_frame_critic.start_frame_critic),
        ("turnaround", turnaround_critic.turnaround_critic),
        ("video_frame", video_frame_critic.video_frame_critic),
    ]
    for name, adapter in adapters:
        with pytest.raises((NotImplementedError, ValueError, TypeError)) as exc_info:
            adapter(structured_output=True)
        assert exc_info.value, (
            f"{name} adapter must raise on missing inputs, not stub-pass"
        )
