import re
from pathlib import Path

import pytest

from recoil.pipeline._lib import prompt_engine
from recoil.pipeline._lib.bible_loader import load_bible
from recoil.pipeline._lib.prompt_engine import (
    BUILDERS,
    IMAGE_PROMPT_BUILDERS,
    TEXT_BEARING_BUILDERS,
    GridType,
    build_grid_prompt,
    build_location_ref_prompt,
    build_previs_prompt,
    build_seedream_prompt,
    build_storyboard_strip_prompt,
    build_two_character_prompt,
    build_universal_expression_matrix,
)


TEXT_SUPPRESSION_RE = re.compile(
    r"(no text|no labels|no captions|no subtitles|no text overlays|"
    r"do not render any text|do not write text)",
    re.IGNORECASE,
)


def _sample_shot() -> dict:
    return {
        "id": 1,
        "shot_id": "SH001",
        "name": "SH001",
        "shot_type": "MS",
        "action": "Two people stand across a quiet room.",
        "emotion": "focused",
        "atmosphere": "late afternoon haze",
        "lighting": "warm side light",
        "characters_in_shot": ["ada"],
        "prompt_data": {
            "shot_type": "MS",
            "camera_movement": "static",
            "kinetic_action": "standing in still tension",
            "prompt_skeleton": {
                "subject_line": "Ada stands beside the workbench.",
                "environment_line": "A practical workshop with shelves and dust.",
                "emotion_line": "Focused and alert.",
            },
            "lighting": {
                "sources": [
                    {
                        "motivator": "window",
                        "quality": "soft",
                        "color_temp": "warm",
                    }
                ]
            },
        },
        "routing_data": {"is_env_only": False},
        "asset_data": {
            "characters": [{"char_id": "ada", "screen_position": "left"}],
            "location_id": "workshop",
        },
    }


def _sample_storyboard() -> dict:
    return {
        "cinematic": "Shot on practical film stock.",
        "location": "A lived-in workshop.",
        "atmosphere": "quiet suspended dust",
        "characters": {
            "ada": {
                "visual": "adult with short dark hair and work jacket",
                "wardrobe": "canvas jacket",
            }
        },
    }


def _sample_bible() -> dict:
    return {
        "characters": {
            "ada": {"display_name": "Ada", "visual_description": "short dark hair"}
        },
        "locations": {
            "workshop": {
                "mood": "quiet practical realism",
                "lighting": "warm window light",
            }
        },
    }


def _sample_core_semantics() -> dict:
    return {
        "characters": [{"char_id": "ada"}],
        "subject_line": "Ada stands beside the workbench, focused and still.",
        "wardrobe": "Canvas jacket over a plain shirt.",
        "action_line": "She studies the room.",
        "kinetic_action": "still tension",
        "environment_line": "A practical workshop with shelves and dust.",
        "film_stock": "Kodak Vision3 500T",
        "lighting_data": {
            "sources": [
                {
                    "motivator": "window",
                    "quality": "soft",
                    "color_temp": "warm",
                }
            ]
        },
    }


def _render_prompt(builder):
    shot = _sample_shot()
    storyboard = _sample_storyboard()
    bible = _sample_bible()
    config = {"aspect_ratio": "16:9"}

    if builder is build_previs_prompt:
        return builder(shot, bible, config)
    if builder is build_seedream_prompt:
        return builder(
            shot,
            bible,
            config,
            _core_semantics=_sample_core_semantics(),
        )
    if builder is build_grid_prompt:
        return builder(
            [shot],
            storyboard,
            {},
            config,
            GridType.ACTION_BURST,
            grid_size="2x2",
        )
    if builder is build_two_character_prompt:
        return builder(
            shot,
            storyboard,
            {
                "name": "Ada",
                "visual": "adult with short dark hair",
                "wardrobe": "canvas jacket",
            },
            {
                "name": "Ben",
                "visual": "adult with silver hair",
                "wardrobe": "dark coat",
            },
            config,
        )
    if builder is build_location_ref_prompt:
        return builder(
            "A practical workshop with shelves and dust.",
            ["Warm window light"],
            config,
        )
    if builder is build_universal_expression_matrix:
        return builder()
    raise AssertionError(f"No prompt fixture for {builder.__name__}")


def test_prompt_bible_shared_lexicon_has_phase_5_tokens():
    lexicon = load_bible()["shared_lexicon"]

    assert (
        lexicon["no_text_footer"]
        == "Do not write text on the image. No subtitles. No captions."
    )
    assert (
        lexicon["anti_ai_look"]
        == (
            "grounded, tactile, physically real — no animation style, "
            "no painterly rendering, no stylized digital glow"
        )
    )

    source = Path(prompt_engine.__file__).read_text()
    assert "anti_ai_look" not in source


def test_image_prompt_builders_constant_covers_dispatch_image_builders():
    dispatch_image_builders = {
        builder
        for (_model_id, modality), builder in BUILDERS.items()
        if modality in {"keyframe", "previz"}
    }

    assert dispatch_image_builders <= set(IMAGE_PROMPT_BUILDERS)
    assert build_storyboard_strip_prompt not in IMAGE_PROMPT_BUILDERS
    assert TEXT_BEARING_BUILDERS == (build_storyboard_strip_prompt,)


@pytest.mark.parametrize(
    "builder",
    IMAGE_PROMPT_BUILDERS,
    ids=lambda builder: builder.__name__,
)
def test_every_image_prompt_builder_emits_text_suppression(builder):
    prompt = _render_prompt(builder)

    assert TEXT_SUPPRESSION_RE.search(prompt), builder.__name__
