"""Foundation validation test.

Verifies:
1. FormatVisualRules and BeatPolicy dataclasses work
2. Puzzle Box visual_rules.py loads and provides correct policies
3. Puzzle Box parser extracts shots from a sample episode
4. Compiler produces valid render manifests
5. Strangler fig routing works (puzzle_box → new path, kill_box → legacy)
"""

import sys
from pathlib import Path

# Ensure recoil root is importable
RECOIL_ROOT = Path(__file__).resolve().parent.parent
if str(RECOIL_ROOT) not in sys.path:
    sys.path.insert(0, str(RECOIL_ROOT))

from visual.format_interface import FormatVisualRules, BeatPolicy  # noqa: E402
from visual.compiler import (  # noqa: E402
    load_visual_rules, load_format_parser, compile_episode_manifest, has_visual_rules,
)


def test_dataclasses():
    """Test FormatVisualRules and BeatPolicy creation."""
    bp = BeatPolicy(max_loras=1, composition_style="FACE_DOMINANT", allow_kinetic_prompting=False)
    assert bp.max_loras == 1
    assert bp.composition_style == "FACE_DOMINANT"

    rules = FormatVisualRules(
        mute_layers=["kinetic_action"],
        default_beat_policy=bp,
    )
    assert rules.is_layer_active("camera_line")
    assert not rules.is_layer_active("kinetic_action")
    assert rules.get_bleed_factor(1) == 0.0
    assert rules.get_bleed_factor(4) == 1.0
    print("  [OK] Dataclasses")


def test_puzzle_box_rules():
    """Test Puzzle Box visual rules load correctly."""
    rules = load_visual_rules("puzzle_box")
    assert rules is not None, "puzzle_box visual_rules.py not found"
    assert "kinetic_action" in rules.mute_layers
    assert rules.supports_grammar is True
    assert rules.grammar_injection_layer == 2

    # Beat policies
    entry_policy = rules.get_beat_policy("ENTRY_IMAGE")
    assert entry_policy.max_loras == 0
    assert entry_policy.composition_style == "ENVIRONMENT"

    voice_policy = rules.get_beat_policy("VOICE")
    assert voice_policy.max_loras == 1
    assert voice_policy.composition_style == "FACE_DOMINANT"

    # Bleed schedule
    assert rules.get_bleed_factor(1) == 0.0
    assert rules.get_bleed_factor(2) == 0.15
    assert rules.get_bleed_factor(4) == 1.0

    # Format questions
    assert len(rules.format_check_questions) >= 2
    print("  [OK] Puzzle Box rules")


def test_puzzle_box_parser():
    """Test Puzzle Box parser on a sample episode."""
    parser = load_format_parser("puzzle_box")
    assert parser is not None, "puzzle_box parser.py not found"

    sample = '''# EP01 — Dead Air

## Metadata
- Exposure: THE MOOD
- Sequence: INCITING (Push)
- Rhythm: Suspended
- VO: Yes
- Ending Type: OBJECT

## NARRATIVE SCRIPT

### [00:00-00:04] ENTRY IMAGE

Rain on glass. Neon bleeds behind it.

### [00:04-00:22] VOICE

[VO: SADIE]
"The number. It hasn't worked in two years."

### [00:22-00:30] LINGER

The phone on the counter.

---

## PIPELINE DIRECTION

### ENTRY IMAGE (4s) — 2 shots
- Shot 1 (2s): WS. Rain-streaked glass, neon city blurred behind.
- Shot 2 (2s): MS. Camera drifts down exterior of building.

### VOICE (18s) — 3 shots
- Shot 3 (6s): MS. Sadie in profile against apartment window. Static frame.
- Shot 4 (6s): CU. Sadie's hand on kitchen counter. Static.
- Shot 5 (6s): WS. The apartment from the doorway. Slow drift right.

### LINGER (8s) — 1 shot
- Shot 6 (8s): ECU. The phone, face down on counter. Hold. No movement.

### Audio Direction
- Ambient bed: Rain on glass, distant city hum
- Spot SFX: Phone vibration, voicemail chime
- VO delivery: Present tense, halting
'''
    result = parser.parse_episode(sample)
    assert result["episode_number"] == 1
    assert result["title"] == "Dead Air"
    assert result["shot_count"] == 6
    assert result["shots"][0]["beat"] == "ENTRY_IMAGE"
    assert result["shots"][0]["shot_type"] == "WS"
    assert result["shots"][2]["beat"] == "VOICE"
    assert "SADIE" in result["shots"][2]["characters"]
    assert result["shots"][5]["beat"] == "LINGER"
    assert result["shots"][5]["shot_type"] == "ECU"
    print("  [OK] Puzzle Box parser")


def test_compiler():
    """Test compiler produces valid manifests."""
    sample = '''# EP01 — Dead Air

## Metadata
- Exposure: THE MOOD
- Rhythm: Suspended

## NARRATIVE SCRIPT

### [00:00-00:04] ENTRY IMAGE
Rain on glass.

---

## PIPELINE DIRECTION

### ENTRY IMAGE (4s) — 2 shots
- Shot 1 (2s): WS. Rain-streaked glass, neon city blurred behind.
- Shot 2 (2s): MS. Camera drifts down exterior. Slow drift.

### VOICE (18s) — 2 shots
- Shot 3 (6s): CU. Sadie in profile. Static.
- Shot 4 (6s): MS. The apartment. Slow drift right.

### LINGER (8s) — 1 shot
- Shot 5 (8s): ECU. Phone on counter. Hold.
'''
    grammars = {
        "SADIE": "85mm telephoto, shallow depth of field, cool cyan",
        "DUSTY": "9.8mm ultra-wide, deep focus, warm amber",
    }

    manifests = compile_episode_manifest(
        episode_text=sample,
        format_name="puzzle_box",
        project_name="afterimage",
        episode_number=1,
        grammars=grammars,
        exposure_level=1,
    )

    assert len(manifests) == 5
    assert manifests[0]["beat"] == "ENTRY_IMAGE"
    assert manifests[0]["composition"]["style"] == "ENVIRONMENT"
    assert manifests[0]["composition"]["max_loras"] == 0
    assert manifests[2]["beat"] == "VOICE"
    assert manifests[2]["composition"]["style"] == "FACE_DOMINANT"
    assert manifests[4]["beat"] == "LINGER"
    assert manifests[4]["payload"]["shot_type"] == "ECU"

    # Verify format questions in validation
    for m in manifests:
        assert len(m["validation"]["format_questions"]) >= 2

    # Verify execution defaults
    assert manifests[0]["execution"]["engine"] == "kling"
    assert manifests[0]["execution"]["aspect_ratio"] == "9:16"

    print("  [OK] Compiler manifests")


def test_strangler_fig_routing():
    """Test routing: puzzle_box → new path, kill_box → legacy."""
    assert has_visual_rules("puzzle_box") is True
    assert has_visual_rules("kill_box") is False
    assert has_visual_rules("kill_box_micro") is False
    print("  [OK] Strangler fig routing")


if __name__ == "__main__":
    print("Foundation Validation Tests")
    print("=" * 40)
    test_dataclasses()
    test_puzzle_box_rules()
    test_puzzle_box_parser()
    test_compiler()
    test_strangler_fig_routing()
    print("=" * 40)
    print("ALL TESTS PASSED")
