"""Tests for the Gemini-specific feedback contract.

NBP forbidden strategies must be filtered. Fallback order must be correct.
forbidden_reroll_strategies takes absolute precedence over allowed_reroll_strategies.
"""
import pytest
from unittest.mock import patch, MagicMock
from pathlib import Path


def _make_verdict(failure_category="identity_drift", severity=3):
    """Create a mock verdict with details dict."""
    v = MagicMock()
    v.details = {
        "failure_category": failure_category,
        "total_severity": severity,
        "shot_type": "MS",
    }
    return v


def test_diagnose_accepts_target_model_param():
    """FeedbackAgent.diagnose() accepts a target_model parameter."""
    from recoil.execution.feedback.agent import FeedbackAgent
    agent = FeedbackAgent(project_id="test")
    # Should not raise TypeError for target_model kwarg
    fix = agent.diagnose(
        verdict=_make_verdict(),
        prompt_sections=[],
        current_refs=[],
        ref_metadata={},
        failed_output_path=Path("/tmp/fake.jpg"),
        attempt_number=1,
        modality="keyframe",
        target_model="gemini-3-pro-image-preview",
    )
    # Fix may be None (fine — we're testing the interface, not the fix)


def test_nbp_forbidden_strategies_are_filtered():
    """NBP model must never receive strategies listed in its
    forbidden_reroll_strategies profile."""
    from recoil.execution.feedback.agent import FeedbackAgent, FeedbackStrategy

    agent = FeedbackAgent(project_id="test")

    # Patch match_fix to return a strategy whose value IS in the forbidden list
    forbidden_fix = MagicMock()
    forbidden_fix.strategy = FeedbackStrategy.STYLE_ANCHOR
    forbidden_fix.confidence = 0.8

    # Mock the model profile to include style_anchor in forbidden list
    mock_profile = {
        "forbidden_reroll_strategies": [
            "style_transfer", "prompt_append", "prompt_rewrite",
            "negative_prompt_injection", "style_anchor", "lvlm_rewrite",
        ],
    }
    with patch("recoil.core.model_profiles.get_profile", return_value=mock_profile):
        with patch("recoil.execution.feedback.fix_registry.match_fix", return_value=forbidden_fix):
            fix = agent.diagnose(
                verdict=_make_verdict(),
                prompt_sections=[],
                current_refs=[],
                ref_metadata={},
                failed_output_path=Path("/tmp/fake.jpg"),
                attempt_number=1,
                modality="keyframe",
                target_model="gemini-3-pro-image-preview",
            )

    # The fix should be filtered out and replaced with a safe fallback
    if fix is not None:
        assert fix.strategy.value not in (
            "style_anchor", "lvlm_rewrite", "prompt_append", "prompt_rewrite",
            "negative_prompt_injection", "style_transfer",
        )


def test_forbidden_takes_precedence_over_allowed():
    """If a strategy is in both allowed and forbidden, forbidden wins."""
    from recoil.execution.feedback.agent import FeedbackAgent

    agent = FeedbackAgent(project_id="test")
    # seed is in allowed_reroll_strategies for NBP — verify it's NOT forbidden
    fix = agent.diagnose(
        verdict=_make_verdict(),
        prompt_sections=[],
        current_refs=[],
        ref_metadata={},
        failed_output_path=Path("/tmp/fake.jpg"),
        attempt_number=2,  # higher attempt to trigger fallbacks
        modality="keyframe",
        target_model="gemini-3-pro-image-preview",
    )
    # If a fix is returned, it must be from the safe fallback order
    if fix is not None:
        safe_strategies = {"seed_reroll", "reduce_refs", "aspect_crop",
                           "anatomy_anchor", "ref_prune_and_anchor",
                           "crop_to_closeup"}
        assert fix.strategy.value in safe_strategies


def test_video_modality_returns_none_with_model_param():
    """Video feedback still returns None (stub) but accepts target_model."""
    from recoil.execution.feedback.agent import FeedbackAgent
    agent = FeedbackAgent(project_id="test")
    fix = agent.diagnose(
        verdict=_make_verdict(),
        prompt_sections=[],
        current_refs=[],
        ref_metadata={},
        failed_output_path=Path("/tmp/fake.mp4"),
        attempt_number=1,
        modality="video",
        target_model="kling-v3",
    )
    assert fix is None


def test_fallback_order_when_prompt_mutation_forbidden():
    """When prompt mutation is forbidden, fallback order is: seed_reroll, reduce_refs, aspect_crop."""
    from recoil.execution.feedback.agent import FeedbackAgent

    agent = FeedbackAgent(project_id="test")

    # Force multiple diagnose calls to exercise the fallback chain
    # by patching match_fix to always return None (no deterministic fix)
    with patch("recoil.execution.feedback.fix_registry.match_fix", return_value=None):
        fix = agent.diagnose(
            verdict=_make_verdict(failure_category="grid_influence", severity=2),
            prompt_sections=[],
            current_refs=[],
            ref_metadata={},
            failed_output_path=Path("/tmp/fake.jpg"),
            attempt_number=3,
            modality="keyframe",
            target_model="gemini-3-pro-image-preview",
        )
    # Should attempt a safe fallback (or None if all exhausted)
    if fix is not None:
        assert fix.strategy.value in ("seed_reroll", "reduce_refs", "aspect_crop", "crop_to_closeup")


def test_new_enum_members_exist():
    """SEED_REROLL, REDUCE_REFS, ASPECT_CROP must exist in FeedbackStrategy."""
    from recoil.execution.feedback.agent import FeedbackStrategy
    assert FeedbackStrategy.SEED_REROLL.value == "seed_reroll"
    assert FeedbackStrategy.REDUCE_REFS.value == "reduce_refs"
    assert FeedbackStrategy.ASPECT_CROP.value == "aspect_crop"


def test_safe_fallback_returns_seed_reroll_first():
    """When no deterministic fix and model has forbidden strategies,
    _safe_fallback should return SEED_REROLL as first choice."""
    from recoil.execution.feedback.agent import FeedbackAgent, FeedbackStrategy

    agent = FeedbackAgent(project_id="test")

    # Directly test _safe_fallback
    forbidden = {"style_transfer", "prompt_append", "prompt_rewrite", "negative_prompt_injection"}
    fix = agent._safe_fallback(forbidden, attempt_number=1, verdict=_make_verdict())

    assert fix is not None
    assert fix.strategy == FeedbackStrategy.SEED_REROLL
