"""End-to-end validation test suite.

Verifies the complete pipeline:
1. Multiple AFTERIMAGE episodes parse and compile
2. Kill Box routing goes to legacy (no visual_rules.py)
3. Starsend still works independently
4. Grammar bleed across exposure levels
5. All new Python files pass syntax check
"""

import sys
from pathlib import Path

RECOIL_ROOT = Path(__file__).resolve().parent.parent
PIPELINE_ROOT = RECOIL_ROOT / "pipeline"

if str(RECOIL_ROOT) not in sys.path:
    sys.path.insert(0, str(RECOIL_ROOT))

from recoil.core.paths import projects_root  # noqa: E402


def test_multi_episode_compile():
    """Compile multiple AFTERIMAGE episodes."""
    from visual.compiler import compile_with_prompt_engine  # noqa: E402
    from visual.grammar_extractor import extract_grammars, get_exposure_level  # noqa: E402

    chars_path = projects_root() / "afterimage" / "bible" / "characters.md"
    grammars = extract_grammars(chars_path) if chars_path.exists() else {}

    episodes_dir = projects_root() / "afterimage" / "episodes"
    if not episodes_dir.exists():
        print("  [SKIP] No episodes directory")
        return

    total_manifests = 0
    episodes_parsed = 0

    for ep_file in sorted(episodes_dir.glob("ep_*.md")):
        ep_num = int(ep_file.stem.split("_")[1])
        text = ep_file.read_text(encoding="utf-8")

        try:
            manifests = compile_with_prompt_engine(
                episode_text=text,
                format_name="puzzle_box",
                project_name="afterimage",
                episode_number=ep_num,
                grammars=grammars,
                exposure_level=get_exposure_level(ep_num),
            )
            total_manifests += len(manifests)
            episodes_parsed += 1
        except Exception as e:
            print(f"  [WARN] EP{ep_num:02d} failed: {e}")

    assert episodes_parsed >= 10, (
        f"Only parsed {episodes_parsed} episodes (expected >= 10)"
    )
    assert total_manifests >= 40, (
        f"Only {total_manifests} total manifests (expected >= 40)"
    )
    print(
        f"  [OK] Multi-episode: {episodes_parsed} episodes "
        f"\u2192 {total_manifests} manifests"
    )


def test_strangler_fig_routing():
    """Verify routing: puzzle_box -> new, kill_box -> legacy."""
    from visual.compiler import has_visual_rules  # noqa: E402

    assert has_visual_rules("puzzle_box") is True, "puzzle_box should route to new path"
    assert has_visual_rules("kill_box") is False, "kill_box should route to legacy"
    assert has_visual_rules("kill_box_micro") is False, (
        "kill_box_micro should route to legacy"
    )
    assert has_visual_rules("nonexistent") is False, (
        "nonexistent format should route to legacy"
    )
    print("  [OK] Strangler fig routing")


def test_starsend_independence():
    """Verify the visual pipeline (formerly starsend/) still compiles."""
    import py_compile

    # Post-shim-deletion (commit 9cce8e8d): vision_check moved from
    # pipeline/lib/ → recoil/core/. step_runner moved from
    # pipeline/orchestrator/ → recoil/execution/.
    # phase-21-22 Phase 1: pipeline/lib/ → pipeline/_lib/.
    key_files = [
        PIPELINE_ROOT / "_lib" / "prompt_engine.py",
        PIPELINE_ROOT / "_lib" / "elements.py",
        RECOIL_ROOT / "core" / "vision_check.py",
        RECOIL_ROOT / "execution" / "step_runner.py",
    ]

    for f in key_files:
        assert f.exists(), f"Pipeline file missing: {f}"

    for f in key_files:
        try:
            py_compile.compile(str(f), doraise=True)
        except py_compile.PyCompileError as e:
            raise AssertionError(f"Pipeline file won't compile: {f}: {e}")

    print("  [OK] Pipeline independence (key files exist and compile)")


def test_grammar_bleed_across_exposures():
    """Test grammar output changes across exposure levels."""
    from visual.compiler import compile_with_prompt_engine  # noqa: E402
    from visual.grammar_extractor import extract_grammars  # noqa: E402

    chars_path = projects_root() / "afterimage" / "bible" / "characters.md"
    grammars = extract_grammars(chars_path) if chars_path.exists() else {}

    if not grammars:
        print("  [SKIP] No grammars extracted")
        return

    ep_path = projects_root() / "afterimage" / "episodes" / "ep_01.md"
    if not ep_path.exists():
        print("  [SKIP] ep_01.md not found")
        return

    text = ep_path.read_text(encoding="utf-8")

    manifests_e1 = compile_with_prompt_engine(
        text,
        "puzzle_box",
        "afterimage",
        1,
        grammars=grammars,
        exposure_level=1,
    )
    manifests_e4 = compile_with_prompt_engine(
        text,
        "puzzle_box",
        "afterimage",
        1,
        grammars=grammars,
        exposure_level=4,
    )

    if manifests_e1 and manifests_e4:
        prompt_e1 = manifests_e1[0]["payload"]["compiled_prompt"]
        prompt_e4 = manifests_e4[0]["payload"]["compiled_prompt"]
        assert prompt_e1, "Exposure 1 prompt is empty"
        assert prompt_e4, "Exposure 4 prompt is empty"
        print("  [OK] Grammar bleed — prompts generated for both exposures")
    else:
        print("  [WARN] Not enough manifests for bleed comparison")


def test_all_new_files_compile():
    """Verify all new Python files in recoil/visual/ pass py_compile."""
    import py_compile

    visual_dir = RECOIL_ROOT / "visual"
    py_files = list(visual_dir.glob("*.py"))

    for f in py_files:
        try:
            py_compile.compile(str(f), doraise=True)
        except py_compile.PyCompileError as e:
            raise AssertionError(f"Compilation failed: {f}: {e}")

    format_files = list((RECOIL_ROOT / "formats" / "puzzle_box").glob("*.py"))
    for f in format_files:
        try:
            py_compile.compile(str(f), doraise=True)
        except py_compile.PyCompileError as e:
            raise AssertionError(f"Compilation failed: {f}: {e}")

    total = len(py_files) + len(format_files)
    print(f"  [OK] All {total} new Python files compile cleanly")


def test_manifest_structure():
    """Verify manifest JSON structure matches the contract."""
    from visual.compiler import compile_with_prompt_engine  # noqa: E402
    from visual.grammar_extractor import extract_grammars  # noqa: E402

    ep_path = projects_root() / "afterimage" / "episodes" / "ep_01.md"
    chars_path = projects_root() / "afterimage" / "bible" / "characters.md"

    if not ep_path.exists():
        print("  [SKIP] ep_01.md not found")
        return

    text = ep_path.read_text(encoding="utf-8")
    grammars = extract_grammars(chars_path) if chars_path.exists() else {}

    manifests = compile_with_prompt_engine(
        text,
        "puzzle_box",
        "afterimage",
        1,
        grammars=grammars,
        exposure_level=1,
    )

    required_keys = {
        "job_id",
        "format",
        "episode",
        "beat",
        "execution",
        "payload",
        "composition",
        "validation",
    }

    for m in manifests:
        missing = required_keys - set(m.keys())
        assert not missing, f"Manifest missing keys: {missing}"
        assert "compiled_prompt" in m["payload"]
        assert "aspect_ratio" in m["execution"]
        assert "format_questions" in m["validation"]

    print(f"  [OK] Manifest structure valid ({len(manifests)} manifests)")


if __name__ == "__main__":
    print("End-to-End Validation")
    print("=" * 50)
    test_all_new_files_compile()
    test_strangler_fig_routing()
    test_starsend_independence()
    test_grammar_bleed_across_exposures()
    test_multi_episode_compile()
    test_manifest_structure()
    print("=" * 50)
    print("ALL E2E TESTS PASSED")
