from __future__ import annotations

from pathlib import Path

from recoil.pipeline._lib.story_gate import (
    ROUTES,
    StoryGatePacket,
    build_image_judge_prompt,
    build_text_stageability_prompt,
)


def _packet() -> StoryGatePacket:
    beats_text = (
        "AUTHORING\n"
        "1. Jade sees CRYO-07 spark and reaches for the manual release.\n"
        "2. The pod hatch opens because Jade pulls the manual release.\n"
    )
    scene_context = (
        "SCENE CONTEXT\n"
        "causal ground truth: CRYO-07 was locked until Jade armed the release.\n"
    )
    generation_prompt = (
        "STYLE BLOCK\n"
        "SCENE CONTEXT\n"
        "causal ground truth: CRYO-07 was locked until Jade armed the release.\n"
        "AUTHORING\n"
        "1. Jade sees CRYO-07 spark and reaches for the manual release.\n"
        "2. The pod hatch opens because Jade pulls the manual release.\n"
    )
    return StoryGatePacket(
        board_id="EP001_CONT_004_v03",
        board_png=Path("EP001_CONT_004_v03.png"),
        grid_cols=2,
        grid_rows=2,
        slots=4,
        generation_prompt=generation_prompt,
        beats_text=beats_text,
        scene_context=scene_context,
        panels=[{"index": 1}, {"index": 2}],
        source_sha256="abc123",
        character_descriptions={"Jade": "Salvager in a pressure jacket."},
    )


def test_text_stageability_prompt_includes_inputs_and_json_contract():
    packet = _packet()

    prompt = build_text_stageability_prompt(packet)

    assert packet.beats_text in prompt
    assert packet.scene_context in prompt
    assert "causal ground truth" in prompt
    assert "FOR EACH numbered beat" in prompt
    assert "single panel without inventing a missing cause" in prompt
    assert "actor" in prompt
    assert "target" in prompt
    assert "visible action" in prompt
    assert "prior state" in prompt
    assert "prior beat or the scene context" in prompt
    assert '"stageable": bool' in prompt
    assert '"check": "causal_setup_present"' in prompt
    assert '"severity": "HARD|SOFT"' in prompt
    assert '"injectable": false' in prompt
    assert "JSON only" in prompt


def test_image_judge_prompt_includes_mandatory_rubric_sections():
    packet = _packet()

    prompt = build_image_judge_prompt(packet)

    assert "ACTOR/TARGET" in prompt
    assert "Describe only what is visible" in prompt
    assert "object_of_gaze_in_frame_and_front" in prompt
    assert "spatially_possible=false" in prompt
    assert (
        "Name the visible or scripted cause in panel N (or the scene context) "
        "that produces the change in panel N+1. If you cannot name one, "
        "FAIL causal_setup_present, severity HARD."
    ) in prompt
    for route in ROUTES:
        assert route in prompt
    assert (
        "injectable = obeyable by the image model without changing what HAPPENS "
        "in the beat"
    ) in prompt
    assert packet.generation_prompt in prompt


def test_image_judge_prompt_includes_packet_text_and_image_ordering():
    packet = _packet()

    prompt = build_image_judge_prompt(packet)

    assert packet.beats_text in prompt
    assert packet.scene_context in prompt
    assert "image 1 = full board" in prompt
    assert "image 2 = panel 1 crop" in prompt
    assert "per-panel crops in index order" in prompt
    assert "AS-SENT GENERATION PROMPT FOR prompt_problem COMPARISON" in prompt
    assert "JSON only" in prompt
    # The image-judge call declares the JSON verdict output contract and pins
    # text_stageability to null (it is merged by the caller, not judged here).
    assert "OUTPUT CONTRACT - JSON only" in prompt
    assert '"text_stageability": null' in prompt
    assert "text_stageability is null in the image call" in prompt
