#!/usr/bin/env python3
"""
Video API routes for listing, streaming, and serving video/audio files.

Features:
- List all processed videos with metadata
- Get detailed metadata for specific video
- Stream video files with Range request support (for seeking)
- Stream audio files
- Serve thumbnails
"""

import json
import logging
from pathlib import Path
from typing import List, Optional, Dict, Any

from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import FileResponse, StreamingResponse
from pydantic import BaseModel

# Import config to get output directory
import sys
root_dir = Path(__file__).parent.parent.parent.parent
sys.path.insert(0, str(root_dir))
from src.config import Config

logger = logging.getLogger("VisualizerBackend.Videos")

router = APIRouter()

# Load config to get output directory
config = Config()
# Fix: Convert to absolute path from project root (3 parents up: routes/ -> backend/ -> visualizer/ -> plx/)
OUTPUT_DIR = (Path(__file__).parent.parent.parent.parent / config.output_dir).resolve()

# === Response Models ===
class VideoListItem(BaseModel):
    """Video list item with basic metadata"""
    video_id: str
    title: str
    duration: float
    processed_date: Optional[str] = None
    thumbnail_url: Optional[str] = None
    has_video: bool = False
    has_audio: bool = False
    segment_count: int = 0
    speaker_count: int = 0


class SegmentInfo(BaseModel):
    """Segment metadata"""
    segment_id: int
    start: float  # Processed time (post intro-skip)
    end: float    # Processed time (post intro-skip)
    duration: float
    speaker: str
    status: str
    quality_score: Optional[float] = None
    # Original YouTube timestamps (with intro offset added back)
    original_start: Optional[float] = None
    original_end: Optional[float] = None


class VideoDetail(BaseModel):
    """Detailed video metadata with all segments"""
    video_id: str
    title: str
    youtube_url: str
    duration: float  # Processed duration (after intro skip)
    original_duration: Optional[float] = None  # Full video duration
    intro_skipped: float = 0.0  # Seconds of intro that was skipped
    processed_date: Optional[str] = None
    segments: List[SegmentInfo]
    speakers: List[str]
    statistics: Dict[str, Any]


# === Helper Functions ===
def get_video_directories() -> List[Path]:
    """Get all video directories in output folder"""
    if not OUTPUT_DIR.exists():
        logger.warning(f"Output directory not found: {OUTPUT_DIR}")
        return []
    
    return [d for d in OUTPUT_DIR.iterdir() if d.is_dir()]


def load_metadata(video_dir: Path) -> Optional[Dict]:
    """Load metadata.json from video directory"""
    metadata_file = video_dir / "metadata.json"
    if not metadata_file.exists():
        return None
    
    try:
        with open(metadata_file, 'r') as f:
            return json.load(f)
    except Exception as e:
        logger.error(f"Failed to load metadata from {metadata_file}: {e}")
        return None


def check_video_files(video_dir: Path, video_id: str) -> tuple[bool, bool]:
    """Check if video and audio files exist"""
    video_file = video_dir / f"{video_id}.mp4"
    audio_file = video_dir / f"{video_id}_trimmed.wav"
    
    return video_file.exists(), audio_file.exists()


# === API Endpoints ===
@router.get("/videos", response_model=List[VideoListItem])
async def list_videos():
    """
    List all processed videos with basic metadata.
    
    Returns:
        List of videos with metadata (title, duration, segments, etc.)
    """
    logger.info("📋 Listing all videos...")
    
    video_dirs = get_video_directories()
    videos = []
    
    for video_dir in video_dirs:
        video_id = video_dir.name
        metadata = load_metadata(video_dir)
        
        if not metadata:
            logger.warning(f"No metadata found for {video_id}, skipping")
            continue
        
        # Check if video/audio files exist
        has_video, has_audio = check_video_files(video_dir, video_id)
        
        # Count segments and speakers
        segments = metadata.get('segments', [])
        speakers = set(seg.get('speaker', 'UNKNOWN') for seg in segments)
        
        # Handle different metadata field names (pipeline uses video_title, original_duration)
        title = metadata.get('title') or metadata.get('video_title', 'Unknown')
        duration = metadata.get('duration') or metadata.get('original_duration') or metadata.get('processed_duration', 0.0)
        
        videos.append(VideoListItem(
            video_id=video_id,
            title=title,
            duration=duration,
            processed_date=metadata.get('processed_date'),
            has_video=has_video,
            has_audio=has_audio,
            segment_count=len(segments),
            speaker_count=len(speakers)
        ))
    
    logger.info(f"✅ Found {len(videos)} videos")
    return videos


@router.get("/videos/{video_id}", response_model=VideoDetail)
async def get_video_details(video_id: str):
    """
    Get detailed metadata for a specific video.
    
    Args:
        video_id: Video identifier
        
    Returns:
        Full video metadata including all segments
    """
    logger.info(f"📋 Getting details for video: {video_id}")
    
    video_dir = OUTPUT_DIR / video_id
    
    if not video_dir.exists():
        raise HTTPException(status_code=404, detail=f"Video {video_id} not found")
    
    metadata = load_metadata(video_dir)
    
    if not metadata:
        raise HTTPException(status_code=404, detail=f"Metadata not found for {video_id}")
    
    # Parse segments - add segment_id if not present (use index)
    # Include original_start/original_end for mapping to real YouTube timestamps
    intro_skipped = metadata.get('intro_skipped', 0.0)
    segments = []
    for idx, seg in enumerate(metadata.get('segments', [])):
        segments.append(SegmentInfo(
            segment_id=seg.get('segment_id', idx),  # Use index if no segment_id
            start=seg.get('start', 0.0),
            end=seg.get('end', 0.0),
            duration=seg.get('duration', 0.0),
            speaker=seg.get('speaker', 'UNKNOWN'),
            status=seg.get('status', 'unknown'),
            quality_score=seg.get('quality_score'),
            # Original timestamps for YouTube sync (if available, otherwise compute from intro_skipped)
            original_start=seg.get('original_start') or (seg.get('start', 0.0) + intro_skipped),
            original_end=seg.get('original_end') or (seg.get('end', 0.0) + intro_skipped)
        ))
    
    # Get unique speakers
    speakers = sorted(set(seg.speaker for seg in segments))
    
    # Calculate statistics
    usable_segments = [s for s in segments if s.status == 'usable']
    total_duration = sum(s.duration for s in segments)
    usable_duration = sum(s.duration for s in usable_segments)
    
    statistics = {
        'total_segments': len(segments),
        'usable_segments': len(usable_segments),
        'total_duration': total_duration,
        'usable_duration': usable_duration,
        'usable_percentage': (usable_duration / total_duration * 100) if total_duration > 0 else 0,
        'speakers': len(speakers),
        'average_segment_duration': total_duration / len(segments) if segments else 0
    }
    
    # Handle different metadata field names (pipeline uses video_title, original_duration)
    title = metadata.get('title') or metadata.get('video_title', 'Unknown')
    # processed_duration = audio after intro/outro trimming (what we actually process)
    # original_duration = full video length on YouTube
    processed_duration = metadata.get('processed_duration') or metadata.get('duration', 0.0)
    original_duration = metadata.get('original_duration') or processed_duration
    
    return VideoDetail(
        video_id=video_id,
        title=title,
        youtube_url=metadata.get('youtube_url', ''),
        duration=processed_duration,
        original_duration=original_duration,
        intro_skipped=intro_skipped,
        processed_date=metadata.get('processed_date'),
        segments=segments,
        speakers=speakers,
        statistics=statistics
    )


@router.get("/videos/{video_id}/stream")
async def stream_video(video_id: str, request: Request):
    """
    Stream video file with Range request support for seeking.
    
    This enables instant seeking in the video player by supporting
    HTTP Range requests (RFC 7233).
    
    Args:
        video_id: Video identifier
        request: FastAPI request object (for Range header)
        
    Returns:
        Video file stream with Range support
    """
    video_dir = OUTPUT_DIR / video_id
    video_file = video_dir / f"{video_id}.mp4"
    
    if not video_file.exists():
        raise HTTPException(status_code=404, detail=f"Video file not found for {video_id}")
    
    # Get file size
    file_size = video_file.stat().st_size
    
    # Check for Range header
    range_header = request.headers.get("range")
    
    if not range_header:
        # No range requested, return full file
        return FileResponse(
            str(video_file),
            media_type="video/mp4",
            headers={
                "Accept-Ranges": "bytes",
                "Content-Length": str(file_size)
            }
        )
    
    # Parse Range header (e.g., "bytes=0-1023")
    try:
        range_str = range_header.replace("bytes=", "")
        range_parts = range_str.split("-")
        
        start = int(range_parts[0]) if range_parts[0] else 0
        end = int(range_parts[1]) if len(range_parts) > 1 and range_parts[1] else file_size - 1
        
        # Validate range
        if start >= file_size or end >= file_size or start > end:
            raise ValueError("Invalid range")
        
        # Calculate content length
        content_length = end - start + 1
        
        # Stream the requested range
        def iter_file():
            with open(video_file, "rb") as f:
                f.seek(start)
                remaining = content_length
                chunk_size = 8192
                
                while remaining > 0:
                    chunk = f.read(min(chunk_size, remaining))
                    if not chunk:
                        break
                    remaining -= len(chunk)
                    yield chunk
        
        return StreamingResponse(
            iter_file(),
            status_code=206,  # Partial Content
            media_type="video/mp4",
            headers={
                "Content-Range": f"bytes {start}-{end}/{file_size}",
                "Content-Length": str(content_length),
                "Accept-Ranges": "bytes"
            }
        )
        
    except Exception as e:
        logger.error(f"Error processing range request: {e}")
        # Return full file on error
        return FileResponse(
            str(video_file),
            media_type="video/mp4",
            headers={
                "Accept-Ranges": "bytes",
                "Content-Length": str(file_size)
            }
        )


@router.get("/videos/{video_id}/audio")
async def stream_audio(video_id: str):
    """
    Stream audio file.
    
    Args:
        video_id: Video identifier
        
    Returns:
        Audio file stream
    """
    video_dir = OUTPUT_DIR / video_id
    audio_file = video_dir / f"{video_id}_trimmed.wav"
    
    if not audio_file.exists():
        raise HTTPException(status_code=404, detail=f"Audio file not found for {video_id}")
    
    return FileResponse(
        str(audio_file),
        media_type="audio/wav",
        filename=f"{video_id}.wav"
    )


@router.get("/videos/{video_id}/thumbnail")
async def get_thumbnail(video_id: str):
    """
    Get video thumbnail (if exists).
    
    Args:
        video_id: Video identifier
        
    Returns:
        Thumbnail image
    """
    video_dir = OUTPUT_DIR / video_id
    
    # Try different thumbnail formats
    for ext in ['jpg', 'jpeg', 'png', 'webp']:
        thumbnail_file = video_dir / f"thumbnail.{ext}"
        if thumbnail_file.exists():
            return FileResponse(
                str(thumbnail_file),
                media_type=f"image/{ext}"
            )
    
    raise HTTPException(status_code=404, detail=f"Thumbnail not found for {video_id}")


@router.get("/videos/{video_id}/segment/{segment_id}")
async def get_segment_audio(video_id: str, segment_id: int):
    """
    Extract and stream a specific segment's audio.
    
    Uses ffmpeg to extract the segment on-the-fly for pure, clean audio.
    Audio is extracted at original quality (16kHz WAV).
    
    Args:
        video_id: Video identifier
        segment_id: Segment index (0-based)
        
    Returns:
        WAV audio file for the specific segment
    """
    import subprocess
    import tempfile
    import os
    
    video_dir = OUTPUT_DIR / video_id
    audio_file = video_dir / f"{video_id}_trimmed.wav"
    metadata_file = video_dir / "metadata.json"
    
    if not audio_file.exists():
        raise HTTPException(status_code=404, detail=f"Audio file not found for {video_id}")
    
    if not metadata_file.exists():
        raise HTTPException(status_code=404, detail=f"Metadata not found for {video_id}")
    
    # Load metadata to get segment info
    try:
        with open(metadata_file, 'r') as f:
            metadata = json.load(f)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to load metadata: {e}")
    
    segments = metadata.get('segments', [])
    
    if segment_id < 0 or segment_id >= len(segments):
        raise HTTPException(status_code=404, detail=f"Segment {segment_id} not found (total: {len(segments)})")
    
    segment = segments[segment_id]
    start_time = segment.get('start', 0)
    end_time = segment.get('end', 0)
    duration = end_time - start_time
    
    if duration <= 0:
        raise HTTPException(status_code=400, detail="Invalid segment duration")
    
    # Create temp file for extracted segment
    # Use a cache directory to avoid re-extraction
    cache_dir = video_dir / "segments_cache"
    cache_dir.mkdir(exist_ok=True)
    
    # Cache filename includes start/end for uniqueness
    cache_file = cache_dir / f"seg_{segment_id}_{start_time:.3f}_{end_time:.3f}.wav"
    
    if not cache_file.exists():
        # Extract segment using ffmpeg
        # -ss before -i for fast seeking
        # -t for duration
        # -c:a pcm_s16le for clean PCM audio
        cmd = [
            "ffmpeg", "-y",
            "-ss", str(start_time),
            "-i", str(audio_file),
            "-t", str(duration),
            "-c:a", "pcm_s16le",
            "-ar", "16000",
            "-ac", "1",
            str(cache_file)
        ]
        
        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                timeout=30
            )
            if result.returncode != 0:
                logger.error(f"FFmpeg error: {result.stderr.decode()}")
                raise HTTPException(status_code=500, detail="Failed to extract segment")
        except subprocess.TimeoutExpired:
            raise HTTPException(status_code=500, detail="Segment extraction timed out")
        except Exception as e:
            raise HTTPException(status_code=500, detail=f"FFmpeg error: {e}")
    
    # Return the cached segment
    speaker = segment.get('speaker', 'UNKNOWN')
    filename = f"{video_id}_seg{segment_id}_{speaker}_{start_time:.1f}s.wav"
    
    return FileResponse(
        str(cache_file),
        media_type="audio/wav",
        filename=filename,
        headers={
            "X-Segment-Start": str(start_time),
            "X-Segment-End": str(end_time),
            "X-Segment-Speaker": speaker,
            "X-Segment-Duration": str(duration)
        }
    )


@router.delete("/videos/{video_id}/segments_cache")
async def clear_segment_cache(video_id: str):
    """
    Clear the segment audio cache for a video.
    
    Args:
        video_id: Video identifier
        
    Returns:
        Status message
    """
    import shutil
    
    video_dir = OUTPUT_DIR / video_id
    cache_dir = video_dir / "segments_cache"
    
    if cache_dir.exists():
        shutil.rmtree(cache_dir)
        return {"status": "ok", "message": f"Cleared cache for {video_id}"}
    
    return {"status": "ok", "message": "No cache to clear"}


@router.get("/videos/{video_id}/download")
async def download_video_zip(video_id: str):
    """
    Download all video files as a ZIP archive.
    
    Includes:
    - metadata.json (segment data, speakers, timestamps)
    - Audio file (16kHz WAV)
    - Video file (MP4, if exists)
    - Thumbnail (if exists)
    
    Args:
        video_id: Video identifier
        
    Returns:
        ZIP file containing all video data
    """
    import zipfile
    import io
    import tempfile
    import os
    
    video_dir = OUTPUT_DIR / video_id
    
    if not video_dir.exists():
        raise HTTPException(status_code=404, detail=f"Video {video_id} not found")
    
    # Check if metadata exists
    metadata_file = video_dir / "metadata.json"
    if not metadata_file.exists():
        raise HTTPException(status_code=404, detail=f"Metadata not found for {video_id}")
    
    # Create a temporary file for the zip
    temp_zip = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
    temp_zip_path = temp_zip.name
    temp_zip.close()
    
    try:
        # Create ZIP file
        with zipfile.ZipFile(temp_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
            # Add metadata.json
            if metadata_file.exists():
                zipf.write(metadata_file, f"{video_id}/metadata.json")
                logger.info(f"Added metadata.json to ZIP")
            
            # Add audio file (trimmed WAV)
            audio_file = video_dir / f"{video_id}_trimmed.wav"
            if audio_file.exists():
                zipf.write(audio_file, f"{video_id}/{audio_file.name}")
                logger.info(f"Added {audio_file.name} to ZIP")
            
            # Add original audio if exists
            original_audio = video_dir / f"{video_id}_original.wav"
            if original_audio.exists():
                zipf.write(original_audio, f"{video_id}/{original_audio.name}")
                logger.info(f"Added {original_audio.name} to ZIP")
            
            # Add video file (MP4)
            video_file = video_dir / f"{video_id}.mp4"
            if video_file.exists():
                zipf.write(video_file, f"{video_id}/{video_file.name}")
                logger.info(f"Added {video_file.name} to ZIP")
            
            # Add thumbnail (try various formats)
            for ext in ['jpg', 'jpeg', 'png', 'webp']:
                thumb_file = video_dir / f"thumbnail.{ext}"
                if thumb_file.exists():
                    zipf.write(thumb_file, f"{video_id}/thumbnail.{ext}")
                    logger.info(f"Added thumbnail.{ext} to ZIP")
                    break
        
        # Get file size for logging
        zip_size = os.path.getsize(temp_zip_path)
        logger.info(f"📦 Created ZIP for {video_id}: {zip_size / 1024 / 1024:.1f} MB")
        
        # Return the ZIP file
        return FileResponse(
            temp_zip_path,
            media_type="application/zip",
            filename=f"{video_id}.zip",
            headers={
                "Content-Disposition": f'attachment; filename="{video_id}.zip"'
            },
            background=lambda: os.unlink(temp_zip_path)  # Clean up temp file after response
        )
        
    except Exception as e:
        # Clean up temp file on error
        if os.path.exists(temp_zip_path):
            os.unlink(temp_zip_path)
        logger.error(f"Error creating ZIP for {video_id}: {e}")
        raise HTTPException(status_code=500, detail=f"Failed to create ZIP: {e}")

