from __future__ import annotations

import sys
from pathlib import Path
from types import SimpleNamespace

import pytest

_RECOIL_ROOT = Path(__file__).resolve().parents[3]
_PIPELINE_ROOT = _RECOIL_ROOT / "pipeline"
for _p in [str(_RECOIL_ROOT), str(_PIPELINE_ROOT)]:
    if _p not in sys.path:
        sys.path.insert(0, _p)

from orchestrator import coverage_validator

from recoil.pipeline._lib import dispatch_payload as dp
from recoil.pipeline._lib.plan_loader import CanonicalShot, CharacterEntry
from recoil.pipeline._lib.prose_validator import (
    Severity,
    verify_authored_prose,
)
from recoil.pipeline._lib.shot_primitive import ShotPrimitive


def _primitive() -> ShotPrimitive:
    return ShotPrimitive(
        shot_id="EP002_PASS_009",
        scene_index=2,
        shot_type="OTS",
        target_editorial_duration_s=6.0,
        intent="Jade and Wren cross the med bay under pressure.",
        camera_side="B",
        screen_direction="left-to-right",
        char_ids=["JADE", "WREN"],
        timing_segments=[
            {"start_s": 0.0, "end_s": 3.0, "intent": "Jade moves first."},
            {"start_s": 3.0, "end_s": 6.0, "intent": "Wren answers."},
        ],
        refs={"manifest": {"identity_1": 1, "identity_2": 2}},
    )


def _strategy() -> SimpleNamespace:
    return SimpleNamespace(name="directed_prose", required_inputs=[])


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 test_verify_authored_prose_blocks_image_token_leak() -> None:
    results = verify_authored_prose(
        "[0:00-0:03] @Image1 pushes in.\n[0:03-0:06] Wren braces.",
        _primitive(),
        _strategy(),
    )

    assert any(
        r.severity == Severity.BLOCK and r.check == "prose_verify_imagen_leak"
        for r in results
    )


def test_coverage_validator_reexports_canonical_severity() -> None:
    assert coverage_validator.Severity.BLOCK is Severity.BLOCK


def test_weak_camera_prose_warns_without_blocking() -> None:
    results = verify_authored_prose(
        "[0:00-0:03] Jade grips the rail, breath tight.\n"
        "[0:03-0:06] Wren plants his hand beside her, jaw locked.",
        _primitive(),
        _strategy(),
    )

    assert not [r for r in results if r.severity == Severity.BLOCK]
    assert any(r.severity == Severity.WARN for r in results)
    assert any(r.check == "prose_verify_camera" for r in results)


def test_verify_block_reauthor_once_then_fallback(
    monkeypatch: pytest.MonkeyPatch,
    caplog: pytest.LogCaptureFixture,
) -> None:
    caplog.set_level("WARNING")
    calls: list[dict] = []

    def fake_author(*_args, **kwargs):
        calls.append(kwargs)
        return (
            "[0:00-0:03] @Image1 moves with Jade.\n"
            "[0:03-0:06] @Image2 answers with Wren."
        )

    monkeypatch.setattr(dp, "author_pass", fake_author)
    monkeypatch.setattr(
        dp,
        "get_builder",
        lambda *args, **kwargs: lambda *a, **k: "TEMPLATE",
    )

    result = 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={},
    )

    assert len(calls) == 2
    assert "prose_author_retry_failures" in calls[1]["project_config"]
    assert result.prompt == "TEMPLATE"
    assert result.strategy == "deterministic_template"
    assert "prose_author_fallback" in caplog.text
    assert "reason=verify_block" in caplog.text

