"""
Video Analysis Module

Analyzes compressed M4V/MP4 videos to verify optimal encoding for internetfilm-manager requirements.
Checks keyframe intervals, color space, bit depth, faststart, and metadata tags.
"""

import subprocess
import json
import os
import re
from pathlib import Path
from typing import Dict, Any, Optional, Tuple

from .config import CONFIG


# Analysis constants
MOOV_SEARCH_SIZE_MB = 8  # Size in MB to search for MOOV/MDAT atoms

# Metadata tag value mappings
MEDIA_TYPE_MAP = {
    '0': 'Movie',
    '9': 'Movie',
    '10': 'TV Show',
    '11': 'Music Video'
}

HD_VIDEO_MAP = {
    '0': 'SD',
    '1': '720p',
    '2': '1080p',
    '3': '4K'
}


def run_ffprobe(video_path: str, *args) -> Dict[str, Any]:
    """
    Run ffprobe command and return parsed JSON output.
    
    Args:
        video_path: Path to the video file
        *args: Additional ffprobe arguments
    
    Returns:
        Parsed JSON output from ffprobe
    """
    cmd = [
        CONFIG['ffprobe'],
        '-v', 'error',
        '-print_format', 'json',
        *args,
        video_path
    ]
    
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        return json.loads(result.stdout)
    except subprocess.CalledProcessError as e:
        print(f"❌ ffprobe Fehler: {e.stderr}")
        return {}
    except json.JSONDecodeError as e:
        print(f"❌ JSON Parse Fehler: {e}")
        return {}


def run_ffmpeg_info(video_path: str) -> str:
    """
    Run ffmpeg -i command to get general information.
    
    Args:
        video_path: Path to the video file
    
    Returns:
        Raw ffmpeg output string
    """
    cmd = [CONFIG['ffmpeg'], '-i', video_path]
    
    try:
        result = subprocess.run(cmd, capture_output=True, text=True)
        # ffmpeg writes info to stderr
        return result.stderr
    except (subprocess.SubprocessError, FileNotFoundError) as e:
        return f"❌ Fehler: {str(e)}"


def check_faststart(video_path: str) -> Tuple[bool, str]:
    """
    Check if the video has faststart enabled (MOOV atom before MDAT).
    
    Args:
        video_path: Path to the video file
    
    Returns:
        Tuple of (is_faststart, explanation)
    """
    # For MP4/M4V files, check if moov atom comes before mdat
    # Note: This is a simple byte search; proper MP4 box parsing would be more robust
    try:
        with open(video_path, 'rb') as f:
            # Read first N MB to find moov and mdat atoms
            data = f.read(MOOV_SEARCH_SIZE_MB * 1024 * 1024)
            
            # Find moov and mdat positions
            moov_pos = data.find(b'moov')
            mdat_pos = data.find(b'mdat')
            
            if moov_pos == -1 or mdat_pos == -1:
                return False, "MOOV oder MDAT Atom nicht gefunden"
            
            if moov_pos < mdat_pos:
                return True, f"✅ MOOV vor MDAT (Position: MOOV={moov_pos}, MDAT={mdat_pos})"
            else:
                return False, f"❌ MDAT vor MOOV (Position: MOOV={moov_pos}, MDAT={mdat_pos})"
                
    except (OSError, IOError) as e:
        return False, f"❌ Fehler beim Prüfen: {str(e)}"


def get_framerate_info(video_path: str) -> Tuple[Optional[str], str]:
    """
    Get framerate information from the video.
    
    Args:
        video_path: Path to the video file
    
    Returns:
        Tuple of (framerate_string, explanation)
    """
    # Get stream data with framerate info
    stream_data = run_ffprobe(video_path, '-show_streams', '-select_streams', 'v:0')
    
    if not stream_data or 'streams' not in stream_data or len(stream_data['streams']) == 0:
        return None, "❌ Konnte Framerate nicht ermitteln"
    
    stream = stream_data['streams'][0]
    
    # Get average frame rate (most reliable)
    avg_frame_rate = stream.get('avg_frame_rate', '')
    r_frame_rate = stream.get('r_frame_rate', '')
    
    # Parse framerate (format is typically "30/1" or "25/1")
    fps = None
    fps_str = avg_frame_rate or r_frame_rate
    
    if fps_str and '/' in fps_str:
        try:
            num, den = fps_str.split('/')
            if int(den) > 0:
                fps = int(num) / int(den)
        except (ValueError, ZeroDivisionError):
            pass
    
    if fps:
        # Check if it's close to common framerates
        if abs(fps - 30) < 0.1:
            status = "✅"
            note = "(optimal für 30 Frames Keyframe-Interval)"
        elif abs(fps - 25) < 0.1:
            status = "ℹ️ "
            note = "(PAL Standard, Keyframe-Interval alle 30 Frames = 1.2 Sekunden)"
        elif abs(fps - 24) < 0.1 or abs(fps - 23.976) < 0.1:
            status = "ℹ️ "
            note = "(Film Standard)"
        elif abs(fps - 60) < 0.1 or abs(fps - 59.94) < 0.1:
            status = "ℹ️ "
            note = "(High Framerate)"
        else:
            status = "ℹ️ "
            note = ""
        
        explanation = f"{status} {fps:.2f} fps {note}".strip()
        return f"{fps:.2f}", explanation
    else:
        return None, "⚠️  Framerate konnte nicht gelesen werden"


def get_video_properties(video_path: str) -> Dict[str, Any]:
    """
    Get detailed video stream properties including color space and bit depth.
    
    Args:
        video_path: Path to the video file
    
    Returns:
        Dictionary with video properties
    """
    data = run_ffprobe(video_path, '-show_streams', '-select_streams', 'v:0')
    
    if not data or 'streams' not in data or len(data['streams']) == 0:
        return {}
    
    stream = data['streams'][0]
    
    # Try to get bit depth from bits_per_raw_sample first
    bit_depth = stream.get('bits_per_raw_sample', 'unknown')
    
    # If not available, try to parse from pixel format
    if bit_depth == 'unknown':
        pix_fmt = stream.get('pix_fmt', '')
        # Parse bit depth from pixel format using regex
        # Match patterns like: yuv420p10le, yuv422p12be (p + 1-2 digits + le/be)
        match = re.search(r'p(\d{1,2})(?:le|be)$', pix_fmt)
        if match:
            bit_depth = match.group(1)
        else:
            # Check for standard 8-bit formats: formats ending in 'p' or YUV formats without digits
            ends_with_p = pix_fmt.endswith('p')
            is_yuv_no_digits = pix_fmt.startswith(('yuv', 'yuvj')) and 'p' in pix_fmt and not re.search(r'\d', pix_fmt)
            if ends_with_p or is_yuv_no_digits:
                bit_depth = '8'
    
    # Extract relevant properties
    properties = {
        'codec_name': stream.get('codec_name', 'unknown'),
        'codec_long_name': stream.get('codec_long_name', 'unknown'),
        'profile': stream.get('profile', 'unknown'),
        'width': stream.get('width', 0),
        'height': stream.get('height', 0),
        'bit_depth': bit_depth,
        'pix_fmt': stream.get('pix_fmt', 'unknown'),
        'color_space': stream.get('color_space', 'unknown'),
        'color_transfer': stream.get('color_transfer', 'unknown'),
        'color_primaries': stream.get('color_primaries', 'unknown'),
        'color_range': stream.get('color_range', 'unknown'),
        'bit_rate': stream.get('bit_rate', 'unknown'),
        'avg_frame_rate': stream.get('avg_frame_rate', 'unknown'),
    }
    
    return properties


def get_metadata_tags(video_path: str) -> Dict[str, str]:
    """
    Extract metadata tags from the video file.
    
    Args:
        video_path: Path to the video file
    
    Returns:
        Dictionary of metadata tags
    """
    data = run_ffprobe(video_path, '-show_format')
    
    if not data or 'format' not in data:
        return {}
    
    tags = data['format'].get('tags', {})
    return tags


def analyze_video(video_path: str) -> Dict[str, Any]:
    """
    Perform complete analysis of a video file.
    
    Args:
        video_path: Path to the video file
    
    Returns:
        Dictionary containing all analysis results
    """
    if not os.path.exists(video_path):
        return {'error': f"Video nicht gefunden: {video_path}"}
    
    print(f"\n🔍 Analysiere Video: {os.path.basename(video_path)}")
    print("=" * 80)
    
    # Get video properties
    print("\n📊 VIDEO EIGENSCHAFTEN")
    print("-" * 80)
    props = get_video_properties(video_path)
    
    result = {
        'video_path': video_path,
        'properties': props,
        'metadata': {},
        'framerate': None,
        'is_faststart': False,
        'analysis_details': {}
    }
    
    if props:
        print(f"Codec:          {props['codec_name']} ({props['codec_long_name']})")
        print(f"Profil:         {props['profile']}")
        print(f"Auflösung:      {props['width']}x{props['height']}")
        print(f"Bit-Tiefe:      {props['bit_depth']} bit")
        print(f"Pixel Format:   {props['pix_fmt']}")
        print(f"Farbraum:       {props['color_space']}")
        print(f"Farbprimaries:  {props['color_primaries']}")
        print(f"Farbtransfer:   {props['color_transfer']}")
        print(f"Farbbereich:    {props['color_range']}")
        print(f"Bitrate:        {props['bit_rate']}")
        print(f"Frame-Rate:     {props['avg_frame_rate']}")
        
        # Info about color space (no warning, just informational)
        if props['color_primaries'] in ['bt2020', 'bt2100']:
            print("✅ Rec.2020 / BT.2020 Farbraum erkannt")
        elif props['color_primaries'] in ['bt709']:
            print("ℹ️  Rec.709 Farbraum (Standard für HD-Content)")
        else:
            print(f"ℹ️  Farbraum: {props['color_primaries']}")
    
    # Get framerate info
    print("\n📹 FRAMERATE")
    print("-" * 80)
    fps, fps_explanation = get_framerate_info(video_path)
    result['framerate'] = fps
    result['analysis_details']['framerate_explanation'] = fps_explanation
    print(fps_explanation)
    
    # Check faststart
    print("\n🚀 FASTSTART (MOOV Position)")
    print("-" * 80)
    is_faststart, faststart_msg = check_faststart(video_path)
    result['is_faststart'] = is_faststart
    result['analysis_details']['faststart_explanation'] = faststart_msg
    print(faststart_msg)
    
    # Get metadata tags
    print("\n🏷️  METADATEN TAGS")
    print("-" * 80)
    tags = get_metadata_tags(video_path)
    result['metadata'] = tags
    
    if tags:
        # Expected tags from internetfilm-manager (core tags that should be present)
        expected_tags = [
            'title', 'artist', 'album', 'genre', 'date', 'comment',
            'description', 'synopsis'
        ]
        
        # Additional informational tags (shown if present)
        additional_tags = [
            'grouping',      # Keywords/Tags
            'encoder',       # Encoded By
            'album_artist',  # Album Artist
            'hd_video',      # HD Video quality
            'media_type',    # Media Kind
            'show',          # TV Show
            'episode_id',    # Episode ID
        ]
        
        filled_tags = []
        missing_tags = []
        
        for tag_name in expected_tags:
            # Check both lowercase and uppercase variants
            tag_value = tags.get(tag_name) or tags.get(tag_name.upper())
            if tag_value:
                filled_tags.append(tag_name)
                print(f"✅ {tag_name:15} = {str(tag_value)[:60]}")
            else:
                missing_tags.append(tag_name)
                print(f"⚠️  {tag_name:15} = (nicht gesetzt)")
        
        # Show additional tags if present
        print("\n📎 ZUSÄTZLICHE TAGS")
        additional_found = False
        for tag_name in additional_tags:
            tag_value = tags.get(tag_name) or tags.get(tag_name.upper())
            if tag_value:
                additional_found = True
                # Format tag display based on type
                if tag_name == 'media_type':
                    type_str = MEDIA_TYPE_MAP.get(str(tag_value), str(tag_value))
                    print(f"ℹ️  {tag_name:15} = {type_str}")
                elif tag_name == 'hd_video':
                    hd_str = HD_VIDEO_MAP.get(str(tag_value), str(tag_value))
                    print(f"ℹ️  {tag_name:15} = {hd_str}")
                else:
                    # Truncate long values (like grouping/keywords)
                    display_value = str(tag_value)
                    if len(display_value) > 60:
                        display_value = display_value[:57] + "..."
                    print(f"ℹ️  {tag_name:15} = {display_value}")
        
        if not additional_found:
            print("   (keine zusätzlichen Tags gefunden)")
        
        result['analysis_details']['filled_tags'] = filled_tags
        result['analysis_details']['missing_tags'] = missing_tags
        
        print(f"\n📋 {len(filled_tags)}/{len(expected_tags)} Kern-Tags ausgefüllt")
    else:
        print("⚠️  Keine Metadaten-Tags gefunden")
        result['analysis_details']['filled_tags'] = []
        result['analysis_details']['missing_tags'] = []
    
    # Summary
    print("\n" + "=" * 80)
    print("📝 ZUSAMMENFASSUNG")
    print("=" * 80)
    
    issues = []
    
    # Show framerate info (informational only)
    if fps:
        print(f"ℹ️  Framerate: {fps} fps")
    
    # Check bit depth
    if props and props.get('bit_depth') == '10':
        print("✅ Bit-Tiefe: 10 bit")
    elif props and props.get('bit_depth') == '8':
        print("ℹ️  Bit-Tiefe: 8 bit (10 bit empfohlen für höchste Qualität)")
    elif props:
        issues.append(f"⚠️  Bit-Tiefe: {props.get('bit_depth')} (Empfohlen: 10 bit)")
    
    # Check faststart
    if is_faststart:
        print("✅ Faststart: Aktiviert (MOOV vor MDAT)")
    else:
        issues.append("❌ Faststart: Nicht aktiviert (MOOV nach MDAT)")
    
    # Color space info (informational only, not an issue)
    if props and props.get('color_primaries') in ['bt2020', 'bt2100']:
        print("✅ Farbraum: Rec.2020 / BT.2020")
    elif props and props.get('color_primaries') in ['bt709']:
        print("ℹ️  Farbraum: Rec.709 (Standard für HD-Content)")
    
    # Check metadata
    if result['analysis_details'].get('filled_tags'):
        filled_count = len(result['analysis_details']['filled_tags'])
        print(f"✅ Metadaten: {filled_count} Kern-Tags ausgefüllt")
    else:
        issues.append("⚠️  Metadaten: Keine Tags gefunden")
    
    if issues:
        print("\n⚠️  PROBLEME:")
        for issue in issues:
            print(f"   {issue}")
    else:
        print("\n✅ Video ist optimal für internetfilm-manager kodiert!")
    
    result['issues'] = issues
    
    return result


def analyze_video_with_raw_output(video_path: str) -> Dict[str, Any]:
    """
    Analyze video and also display raw tool output.
    
    Args:
        video_path: Path to the video file
    
    Returns:
        Dictionary containing analysis results
    """
    print("\n" + "=" * 80)
    print("🔧 RAW TOOL OUTPUT (ffmpeg -i)")
    print("=" * 80)
    raw_output = run_ffmpeg_info(video_path)
    print(raw_output)
    
    print("\n" + "=" * 80)
    print("🔍 STRUKTURIERTE ANALYSE")
    print("=" * 80)
    
    return analyze_video(video_path)
