from __future__ import annotations

import pytest

from recoil.pipeline._lib import dispatch_payload as dp
from recoil.pipeline._lib.author_strategies import AuthorInputError
from recoil.pipeline._lib.plan_loader import CanonicalShot, CharacterEntry


@pytest.fixture(autouse=True)
def _reset_author_breaker(monkeypatch: pytest.MonkeyPatch) -> None:
    dp._reset_breaker_state()
    monkeypatch.setattr(
        dp,
        "get_builder",
        lambda *args, **kwargs: lambda *a, **k: "TEMPLATE",
    )
    yield
    dp._reset_breaker_state()


def _shot(shot_id: str, char_id: str, duration_s: float = 3.0) -> CanonicalShot:
    raw = {
        "shot_id": shot_id,
        "scene_index": 2,
        "duration_s": duration_s,
        "shot_type": "OTS",
        "camera_side": "B",
        "screen_direction": "left-to-right",
        "source_text": f"{char_id.title()} moves through pressure.",
        "asset_data": {"characters": [char_id]},
        "prompt_data": {
            "shot_type": "OTS",
            "action_line": f"{char_id.title()} crosses the med bay.",
            "emotion_line": "Breath held tight.",
        },
    }
    return CanonicalShot(
        shot_id=shot_id,
        scene_index=2,
        sequence_id=None,
        pipeline="video",
        previs_model="gemini-3-pro-image-preview",
        video_model="seeddance-2.0",
        location_id="tartarus_med_bay",
        characters=[CharacterEntry(char_id=char_id)],
        shot_type="OTS",
        duration_s=duration_s,
        is_env_only=False,
        has_dialogue=False,
        aspect_ratio="9:16",
        raw=raw,
    )


def _ctx() -> dp.PayloadContext:
    jade = _shot("EP002_SH01", "JADE")
    wren = _shot("EP002_SH02", "WREN")
    return dp.PayloadContext(
        project="tartarus",
        modality="r2v_multi",
        shot_id="EP002_PASS_009",
        shot=jade,
        batch_shots=[jade, wren],
        model_id="seeddance-2.0",
        bible={},
    )


def _call_author_builder() -> dp.AuthorPromptResult:
    return dp._build_author_aware_prompt(
        _ctx(),
        model_id="seeddance-2.0",
        ref_manifest={"identity_1": 1, "identity_2": 2},
        segment_timestamps=[0.0, 3.0],
        primitive_segment_timestamps=[(0.0, 3.0), (3.0, 6.0)],
        bible={},
        project_config={},
    )


def _raise_author_call(*_args, **_kwargs):
    raise dp.AuthorCallError("opus failed")


def test_third_consecutive_author_call_failure_raises(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(dp, "author_pass", _raise_author_call)

    first = _call_author_builder()
    second = _call_author_builder()

    assert first.fallback is True
    assert first.prompt == "TEMPLATE"
    assert second.fallback is True
    assert second.prompt == "TEMPLATE"

    with pytest.raises(dp.DispatchPayloadError, match="prose author systemic failure"):
        _call_author_builder()


def test_successful_authored_prompt_resets_author_call_breaker(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(dp, "author_pass", _raise_author_call)
    assert _call_author_builder().fallback is True
    assert _call_author_builder().fallback is True

    monkeypatch.setattr(
        dp,
        "author_pass",
        lambda *args, **kwargs: (
            "[0:00-0:03] Jade pushes through the med bay.\n"
            "[0:03-0:06] Wren braces beside Jade."
        ),
    )
    monkeypatch.setattr(dp, "verify_authored_prose", lambda *args, **kwargs: [])
    monkeypatch.setattr(
        dp,
        "bind_named_prose",
        lambda *args, **kwargs: dp.BoundPrompt(
            text="Jade pushes through the med bay. Wren braces beside Jade.",
            payload_refs={},
        ),
    )

    success = _call_author_builder()
    assert success.fallback is False

    monkeypatch.setattr(dp, "author_pass", _raise_author_call)
    assert _call_author_builder().fallback is True
    assert _call_author_builder().fallback is True


def test_author_input_fallback_resets_author_call_breaker(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(dp, "author_pass", _raise_author_call)
    assert _call_author_builder().fallback is True
    assert _call_author_builder().fallback is True

    def raise_author_input(*_args, **_kwargs):
        raise AuthorInputError("missing input")

    monkeypatch.setattr(dp, "author_pass", raise_author_input)
    author_input = _call_author_builder()
    assert author_input.fallback is True
    assert author_input.prompt == "TEMPLATE"

    monkeypatch.setattr(dp, "author_pass", _raise_author_call)
    after_reset = _call_author_builder()
    assert after_reset.fallback is True
    assert after_reset.prompt == "TEMPLATE"
