#!/usr/bin/env python3
"""
Test the full effects pipeline: segment -> effect -> output

This tests that effects can be applied to video segments without
producing "No video stream found" errors.
"""

import subprocess
import tempfile
import sys
from pathlib import Path

# Add parent to path
sys.path.insert(0, str(Path(__file__).parent))

import numpy as np
from sexp_effects import (
    get_interpreter,
    load_effects_dir,
    run_effect,
    list_effects,
)


def create_test_video(path: Path, duration: float = 1.0, size: str = "64x64") -> bool:
    """Create a short test video using ffmpeg."""
    cmd = [
        "ffmpeg", "-y",
        "-f", "lavfi", "-i", f"testsrc=duration={duration}:size={size}:rate=10",
        "-c:v", "libx264", "-preset", "ultrafast",
        str(path)
    ]
    result = subprocess.run(cmd, capture_output=True)
    if result.returncode != 0:
        print(f"Failed to create test video: {result.stderr.decode()}")
        return False
    return True


def segment_video(input_path: Path, output_path: Path, start: float, duration: float) -> bool:
    """Segment a video file."""
    cmd = [
        "ffmpeg", "-y", "-i", str(input_path),
        "-ss", str(start), "-t", str(duration),
        "-c:v", "libx264", "-preset", "ultrafast",
        "-c:a", "aac",
        str(output_path)
    ]
    result = subprocess.run(cmd, capture_output=True)
    if result.returncode != 0:
        print(f"Failed to segment video: {result.stderr.decode()}")
        return False

    # Verify output has video stream
    probe_cmd = [
        "ffprobe", "-v", "quiet", "-print_format", "json",
        "-show_streams", str(output_path)
    ]
    probe_result = subprocess.run(probe_cmd, capture_output=True, text=True)
    import json
    probe_data = json.loads(probe_result.stdout)

    has_video = any(
        s.get("codec_type") == "video"
        for s in probe_data.get("streams", [])
    )
    if not has_video:
        print(f"Segment has no video stream!")
        return False

    return True


def run_effect_on_video(effect_name: str, input_path: Path, output_path: Path) -> bool:
    """Run a sexp effect on a video file using frame processing."""
    import json

    # Get video info
    probe_cmd = [
        "ffprobe", "-v", "quiet", "-print_format", "json",
        "-show_streams", str(input_path)
    ]
    probe_result = subprocess.run(probe_cmd, capture_output=True, text=True)
    probe_data = json.loads(probe_result.stdout)

    video_stream = None
    for stream in probe_data.get("streams", []):
        if stream.get("codec_type") == "video":
            video_stream = stream
            break

    if not video_stream:
        print(f"  Input has no video stream: {input_path}")
        return False

    width = int(video_stream["width"])
    height = int(video_stream["height"])
    fps_str = video_stream.get("r_frame_rate", "10/1")
    if "/" in fps_str:
        num, den = fps_str.split("/")
        fps = float(num) / float(den)
    else:
        fps = float(fps_str)

    # Read frames, process, write
    read_cmd = ["ffmpeg", "-i", str(input_path), "-f", "rawvideo", "-pix_fmt", "rgb24", "-"]
    write_cmd = [
        "ffmpeg", "-y",
        "-f", "rawvideo", "-pix_fmt", "rgb24",
        "-s", f"{width}x{height}", "-r", str(fps),
        "-i", "-",
        "-c:v", "libx264", "-preset", "ultrafast",
        str(output_path)
    ]

    read_proc = subprocess.Popen(read_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
    write_proc = subprocess.Popen(write_cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE)

    frame_size = width * height * 3
    frame_count = 0
    state = {}

    while True:
        frame_data = read_proc.stdout.read(frame_size)
        if len(frame_data) < frame_size:
            break

        frame = np.frombuffer(frame_data, dtype=np.uint8).reshape((height, width, 3))
        processed, state = run_effect(effect_name, frame, {'_time': frame_count / fps}, state)
        write_proc.stdin.write(processed.tobytes())
        frame_count += 1

    read_proc.stdout.close()
    write_proc.stdin.close()
    read_proc.wait()
    write_proc.wait()

    if write_proc.returncode != 0:
        print(f"  FFmpeg encode failed: {write_proc.stderr.read().decode()}")
        return False

    return frame_count > 0


def test_effect_pipeline(effect_name: str, tmpdir: Path) -> tuple:
    """
    Test full pipeline: create video -> segment -> apply effect

    Returns (success, error_message)
    """
    # Create test video
    source_video = tmpdir / "source.mp4"
    if not create_test_video(source_video, duration=1.0, size="64x64"):
        return False, "Failed to create source video"

    # Segment it (simulate what the recipe does)
    segment_video_path = tmpdir / "segment.mp4"
    if not segment_video(source_video, segment_video_path, start=0.2, duration=0.5):
        return False, "Failed to segment video"

    # Check segment file exists and has content
    if not segment_video_path.exists():
        return False, "Segment file doesn't exist"
    if segment_video_path.stat().st_size < 100:
        return False, f"Segment file too small: {segment_video_path.stat().st_size} bytes"

    # Apply effect
    output_video = tmpdir / "output.mp4"
    try:
        if not run_effect_on_video(effect_name, segment_video_path, output_video):
            return False, "Effect processing failed"
    except Exception as e:
        return False, str(e)

    # Verify output
    if not output_video.exists():
        return False, "Output file doesn't exist"
    if output_video.stat().st_size < 100:
        return False, f"Output file too small: {output_video.stat().st_size} bytes"

    return True, None


def main():
    print("=" * 60)
    print("Effects Pipeline Test")
    print("=" * 60)

    # Load effects
    effects_dir = Path(__file__).parent / "sexp_effects" / "effects"
    load_effects_dir(str(effects_dir))

    effects = list_effects()
    print(f"Testing {len(effects)} effects through segment->effect pipeline\n")

    passed = []
    failed = []

    # Test multi-input effects separately
    multi_input_effects = ("blend", "layer")
    print("\nTesting multi-input effects...")
    from sexp_effects.interpreter import get_interpreter
    interp = get_interpreter()
    frame_a = np.random.randint(0, 255, (64, 64, 3), dtype=np.uint8)
    frame_b = np.random.randint(0, 255, (64, 64, 3), dtype=np.uint8)

    for name in multi_input_effects:
        try:
            interp.global_env.set('frame-a', frame_a.copy())
            interp.global_env.set('frame-b', frame_b.copy())
            interp.global_env.set('frame', frame_a.copy())
            result, state = interp.run_effect(name, frame_a.copy(), {'_time': 0.5}, {})
            if isinstance(result, np.ndarray) and result.shape == frame_a.shape:
                passed.append(name)
                print(f"  {name}: OK")
            else:
                failed.append((name, f"Bad output shape: {result.shape if hasattr(result, 'shape') else type(result)}"))
                print(f"  {name}: FAILED - bad shape")
        except Exception as e:
            failed.append((name, str(e)))
            print(f"  {name}: FAILED - {e}")

    print("\nTesting single-input effects through pipeline...")

    # Test each effect
    for effect_name in sorted(effects):
        # Skip multi-input effects (already tested above)
        if effect_name in multi_input_effects:
            continue

        with tempfile.TemporaryDirectory() as tmpdir:
            tmpdir = Path(tmpdir)

            success, error = test_effect_pipeline(effect_name, tmpdir)

            if success:
                passed.append(effect_name)
                print(f"  {effect_name}: OK")
            else:
                failed.append((effect_name, error))
                print(f"  {effect_name}: FAILED - {error}")

    print()
    print("=" * 60)
    print(f"Pipeline test: {len(passed)} passed, {len(failed)} failed")
    if failed:
        print("\nFailed effects:")
        for name, error in failed:
            print(f"  {name}: {error}")
    print("=" * 60)

    return len(failed) == 0


if __name__ == "__main__":
    success = main()
    sys.exit(0 if success else 1)
