"""
Metadata Indexing Module

Indexes all video files on the rclone server and extracts metadata.
Generates a CSV file with all metadata for analysis and reporting.
"""

import os
import subprocess
import csv
import json
import tempfile
from typing import List, Dict, Any, Optional

from .config import CONFIG


# CSV fieldnames for video metadata index
CSV_FIELDNAMES = [
    "filename",
    "filepath",
    "dmg_filepath",
    "title",
    "artist",
    "album",
    "album_artist",
    "genre",
    "year",
    "date",
    "description",
    "long_description",
    "comment",
    "grouping",
    "encoder",
    "resolution",
    "codec",
    "duration",
    "size_bytes",
    "bitrate",
]


def list_all_videos_on_server() -> List[str]:
    """
    List all M4V/MP4 video files on the rclone server recursively.
    
    Returns:
        List of relative file paths on the server
    """
    rclone_root = CONFIG.get("rclone_root")
    if not rclone_root:
        raise ValueError("rclone_root ist nicht in der Konfiguration definiert")
    
    print(f"🔍 Suche nach Videos auf {rclone_root}...")
    
    # Use rclone lsf with recursive flag to list all files
    cmd = ["rclone", "lsf", rclone_root, "-R", "--files-only"]
    
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        all_files = result.stdout.strip().splitlines()
        
        # Filter for video files (m4v, mp4)
        video_extensions = {'.m4v', '.mp4'}
        video_files = [
            f for f in all_files 
            if any(f.lower().endswith(ext) for ext in video_extensions)
        ]
        
        print(f"✅ {len(video_files)} Videos gefunden")
        return video_files
    
    except subprocess.CalledProcessError as e:
        print(f"❌ Fehler beim Auflisten der Videos: {e.stderr}")
        return []


def list_all_dmg_archives_on_server() -> List[str]:
    """
    List all DMG archive files on the rclone master server recursively.
    
    Returns:
        List of relative file paths on the server
    """
    rclone_master_root = CONFIG.get("rclone_master_root")
    if not rclone_master_root:
        # Fallback to rclone_root if master not configured
        rclone_master_root = CONFIG.get("rclone_root")
    
    if not rclone_master_root:
        raise ValueError("rclone_master_root ist nicht in der Konfiguration definiert")
    
    print(f"🔍 Suche nach DMG-Archiven auf {rclone_master_root}...")
    
    # Use rclone lsf with recursive flag to list all files
    cmd = ["rclone", "lsf", rclone_master_root, "-R", "--files-only"]
    
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        all_files = result.stdout.strip().splitlines()
        
        # Filter for DMG files
        dmg_files = [
            f for f in all_files 
            if f.lower().endswith('.dmg')
        ]
        
        print(f"✅ {len(dmg_files)} DMG-Archive gefunden")
        return dmg_files
    
    except subprocess.CalledProcessError as e:
        print(f"❌ Fehler beim Auflisten der DMG-Archive: {e.stderr}")
        return []


def extract_video_id_from_comment(comment: str) -> Optional[str]:
    """
    Extract video ID (platform_id) from the comment field.
    
    The comment field typically contains the video ID in format "platform_id"
    (e.g., "youtube_abc123", "vimeo_12345678").
    
    Args:
        comment: Comment field from video metadata
    
    Returns:
        Video ID in format "platform_id" or None if not found
    """
    if not comment:
        return None
    
    # Pattern: platform_id format (e.g., youtube_abc123)
    # Platform must be at least 3 chars (lowercase letters only)
    # ID must be at least 3 chars and contain at least one alphanumeric
    import re
    pattern = r'\b([a-z]{3,}_[a-zA-Z0-9][a-zA-Z0-9_-]{2,})\b'
    match = re.search(pattern, comment)
    
    if match:
        return match.group(1)
    
    return None


def match_dmg_to_video(video_id: Optional[str], dmg_files: List[str]) -> Optional[str]:
    """
    Find the DMG archive that matches a video ID.
    
    DMG filenames follow the pattern: YYYY-MM-DD_platform_id.dmg
    (e.g., "2025-05-15_youtube_abc123.dmg")
    
    Args:
        video_id: Video ID in format "platform_id" (e.g., "youtube_abc123")
        dmg_files: List of relative DMG file paths
    
    Returns:
        Relative path to matching DMG file, or None if not found
    """
    if not video_id:
        return None
    
    # Look for DMG files that contain the video ID as a complete token
    # The video_id should appear followed by .dmg extension
    # This approach is simple and works for the standard naming pattern
    import re
    # Pattern: _video_id.dmg ensures we match the full ID
    # This avoids matching "youtube_abc" when looking for "youtube_abc123"
    pattern = re.compile(
        r'_' + re.escape(video_id) + r'\.dmg'
    )
    
    for dmg_file in dmg_files:
        # Get just the filename without path
        basename = os.path.basename(dmg_file)
        
        # Check if video_id matches in the expected pattern
        if pattern.search(basename):
            return dmg_file
    
    return None


def get_remote_file_size(remote_path: str) -> Optional[int]:
    """
    Get the actual file size from rclone without downloading the file.
    
    Args:
        remote_path: Full rclone path (e.g., "lyssach-nas:/Internetfilme/Patrick/video.m4v")
    
    Returns:
        File size in bytes, or None if unable to determine
    """
    try:
        # Use rclone lsjson to get file info including size
        cmd = ["rclone", "lsjson", remote_path]
        result = subprocess.run(cmd, capture_output=True, text=True, check=False, timeout=10)
        
        # Check if command succeeded
        if result.returncode != 0:
            return None
        
        # Parse JSON response
        data = json.loads(result.stdout)
        
        # lsjson returns an array with one element for a single file
        if data and len(data) > 0:
            return data[0].get("Size", None)
        
        return None
    except Exception:
        # Catch all exceptions (TimeoutExpired, JSONDecodeError, network errors, etc.)
        # If we can't get the size, return None and let ffprobe's size be used as fallback
        return None


def extract_metadata_from_remote_video(remote_path: str, download_size_mb: int = 32) -> Dict[str, Any]:
    """
    Extract metadata from a video file on the rclone server.
    Downloads the beginning of the file to extract metadata (faststart enabled).
    
    Args:
        remote_path: Full rclone path (e.g., "lyssach-nas:/Internetfilme/Patrick/video.m4v")
        download_size_mb: Size in MB to download from the beginning (default: 32)
    
    Returns:
        Dictionary with metadata fields
    """
    ffprobe = CONFIG.get("ffprobe", "ffprobe")
    
    # Get the actual file size from rclone before downloading
    actual_file_size = get_remote_file_size(remote_path)
    
    # Create temp file for partial download
    temp_fd, temp_path = tempfile.mkstemp(suffix='.m4v')
    
    # Initialize process variables
    rclone_process = None
    head_process = None
    
    try:
        # Download the first N MB using rclone cat with head
        # Since videos have faststart, metadata is at the beginning
        # For large files (5GB+), more data may be needed
        # Use subprocess.Popen to avoid shell injection
        rclone_cmd = ["rclone", "cat", remote_path]
        download_bytes = download_size_mb * 1024 * 1024
        head_cmd = ["head", "-c", str(download_bytes)]
        
        # Pipe rclone cat to head
        rclone_process = subprocess.Popen(
            rclone_cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        
        head_process = subprocess.Popen(
            head_cmd,
            stdin=rclone_process.stdout,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        
        rclone_process.stdout.close()  # Allow rclone to receive SIGPIPE
        
        # Read output and write to temp file
        with open(temp_path, 'wb') as f:
            output, head_stderr = head_process.communicate(timeout=30)
            f.write(output)
        
        # Wait for rclone process to complete
        rclone_process.wait(timeout=5)
        
        # Get file size to verify we got data
        temp_file_size = os.path.getsize(temp_path)
        
        # Check if we got data (error if either process failed)
        # FIX: rclone exit code -13 (SIGPIPE) ist normal, wenn head die Pipe schließt.
        # Wir ignorieren diesen Fehler, solange head erfolgreich war.
        rclone_ok = (rclone_process.returncode == 0) or (rclone_process.returncode == -13)
        
        if head_process.returncode != 0 or not rclone_ok:
            # Try to get stderr from rclone process if it's still available
            rclone_stderr = b''
            try:
                if rclone_process.stderr and not rclone_process.stderr.closed:
                    rclone_stderr = rclone_process.stderr.read()
            except (OSError, ValueError, AttributeError):
                # stderr not available or already closed
                pass
            
            error_msg = f"Failed to download file header (downloaded: {temp_file_size} bytes, rclone exit: {rclone_process.returncode}, head exit: {head_process.returncode})"
            if rclone_stderr:
                error_msg += f" - rclone stderr: {rclone_stderr.decode('utf-8', errors='ignore')[:200]}"
            if head_stderr:
                error_msg += f" - head stderr: {head_stderr.decode('utf-8', errors='ignore')[:200]}"
            
            return {
                "filename": os.path.basename(remote_path),
                "filepath": remote_path,
                "error": error_msg
            }
        
        # Check if we got any data
        if temp_file_size == 0:
            return {
                "filename": os.path.basename(remote_path),
                "filepath": remote_path,
                "error": "Downloaded 0 bytes - file may be empty or inaccessible"
            }
        
        # Now analyze the temp file with ffprobe
        ffprobe_cmd = [
            ffprobe,
            "-v", "error",
            "-print_format", "json",
            "-show_format",
            "-show_streams",
            "-select_streams", "v:0",
            temp_path
        ]
        
        # Initialize result variable for exception handlers
        result = None
        
        try:
            result = subprocess.run(
                ffprobe_cmd,
                capture_output=True,
                text=True,
                timeout=10,
                check=False  # Handle return codes manually
            )
            
            # Check if ffprobe succeeded
            if result.returncode != 0:
                error_detail = f"ffprobe failed (exit code {result.returncode})"
                if result.stderr:
                    error_detail += f" - stderr: {result.stderr[:300]}"
                if result.stdout:
                    error_detail += f" - stdout: {result.stdout[:100]}"
                
                # Check if this is a header parsing error that might be fixed with more data
                header_error_indicators = [
                    "error reading header",
                    "contradictionary STSC and STCO",
                    "moov atom not found",
                    "Invalid data found when processing input"
                ]
                stderr_text = result.stderr if result.stderr else ""
                is_header_error = any(indicator in stderr_text for indicator in header_error_indicators)
                
                return {
                    "filename": os.path.basename(remote_path),
                    "filepath": remote_path,
                    "error": error_detail,
                    "retry_with_more_data": is_header_error
                }
            
            # Check if we got valid JSON
            if not result.stdout.strip():
                return {
                    "filename": os.path.basename(remote_path),
                    "filepath": remote_path,
                    "error": f"ffprobe returned empty output. stderr: {result.stderr[:200]}"
                }
            
            data = json.loads(result.stdout)
            metadata = parse_metadata_from_ffprobe(data, remote_path, actual_file_size)
            
            return metadata
        
        except json.JSONDecodeError as e:
            # result is guaranteed to be defined here since JSONDecodeError only occurs after json.loads
            output_preview = result.stdout[:200] if result.stdout else 'N/A'
            return {
                "filename": os.path.basename(remote_path),
                "filepath": remote_path,
                "error": f"Invalid JSON from ffprobe: {str(e)[:100]} - Output: {output_preview}"
            }
    
    except subprocess.TimeoutExpired as e:
        # Timeout can occur during download or ffprobe
        return {
            "filename": os.path.basename(remote_path),
            "filepath": remote_path,
            "error": f"Timeout during processing (exceeded {e.timeout}s)"
        }
    except Exception as e:
        return {
            "filename": os.path.basename(remote_path),
            "filepath": remote_path,
            "error": str(e)[:100]
        }
    
    finally:
        # Ensure both processes are terminated
        for proc in [rclone_process, head_process]:
            if proc is not None:
                try:
                    proc.terminate()
                    proc.wait(timeout=2)
                except (ProcessLookupError, subprocess.TimeoutExpired):
                    pass
        
        # Clean up temp file
        try:
            os.close(temp_fd)
        except OSError:
            pass
        try:
            os.unlink(temp_path)
        except OSError:
            pass


def extract_metadata_with_retry(remote_path: str) -> Dict[str, Any]:
    """
    Extract metadata from a remote video with retry logic for large files.
    
    For very large files (5GB+), the MP4/M4V header may be larger than expected.
    This function first tries with 32MB, and if it fails with a header parsing error,
    retries with 64MB and then 128MB.
    
    Args:
        remote_path: Full rclone path to the video file
    
    Returns:
        Dictionary with metadata fields or error information
    """
    # Try with increasing sizes: 32MB, 64MB, 128MB
    sizes_to_try = [32, 64, 128]
    
    for i, size_mb in enumerate(sizes_to_try):
        is_last_attempt = (i == len(sizes_to_try) - 1)
        
        # Try to extract metadata with current size
        result = extract_metadata_from_remote_video(remote_path, download_size_mb=size_mb)
        
        # Check if we got an error that suggests we need more data
        if "error" in result and result.get("retry_with_more_data", False) and not is_last_attempt:
            # Retry with larger size
            continue
        
        # Either success or final attempt - return result
        # Remove internal retry flag before returning
        if "retry_with_more_data" in result:
            del result["retry_with_more_data"]
        
        return result
    
    # Fallback - should never reach here due to loop logic, but provide safety
    return {
        "filename": os.path.basename(remote_path),
        "filepath": remote_path,
        "error": "Failed to extract metadata after all retry attempts"
    }


def parse_metadata_from_ffprobe(data: Dict[str, Any], remote_path: str, actual_file_size: Optional[int] = None) -> Dict[str, Any]:
    """
    Parse ffprobe JSON output and extract relevant metadata.
    
    Args:
        data: Parsed JSON from ffprobe
        remote_path: Full rclone path for reference
        actual_file_size: Actual file size in bytes from rclone (if available)
    
    Returns:
        Dictionary with standardized metadata fields
    """
    format_data = data.get("format", {})
    tags = format_data.get("tags", {})
    
    # Get video stream info
    streams = data.get("streams", [])
    video_stream = streams[0] if streams else {}
    
    # Create case-insensitive tag lookup for efficiency
    tags_lower = {k.lower(): v for k, v in tags.items()}
    
    def get_tag(key: str) -> str:
        """Get tag value case-insensitively"""
        return str(tags_lower.get(key.lower(), ""))
    
    # Extract video properties
    width = video_stream.get("width", 0)
    height = video_stream.get("height", 0)
    codec = video_stream.get("codec_name", "")
    
    # Get date once and derive year
    date_value = get_tag("date")
    year_value = date_value[:4] if date_value else ""
    
    # Get description fields
    description = get_tag("description")
    long_desc = get_tag("longdesc") or get_tag("synopsis")
    
    # Get encoder fields
    encoder = get_tag("encoded_by") or get_tag("encoder")
    
    # Use actual file size if available, otherwise fall back to ffprobe's size
    # (which will be incorrect for partial downloads)
    # Ensure consistent type handling: actual_file_size is int, ffprobe returns string
    if actual_file_size is not None:
        file_size = actual_file_size
    else:
        # ffprobe returns size as string, convert to int if possible
        ffprobe_size = format_data.get("size", "")
        if ffprobe_size:
            try:
                file_size = int(ffprobe_size)
            except (ValueError, TypeError):
                file_size = ffprobe_size  # Keep as string if conversion fails
        else:
            file_size = ""
    
    # Build metadata dictionary
    metadata = {
        "filename": os.path.basename(remote_path),
        "filepath": remote_path,
        "title": get_tag("title"),
        "artist": get_tag("artist"),
        "album": get_tag("album"),
        "album_artist": get_tag("album_artist"),
        "genre": get_tag("genre"),
        "date": date_value,
        "year": year_value,
        "description": description,
        "long_description": long_desc,
        "comment": get_tag("comment"),
        "grouping": get_tag("grouping"),
        "encoder": encoder,
        "resolution": f"{width}x{height}" if width and height else "",
        "codec": codec,
        "duration": format_data.get("duration", ""),
        "size_bytes": str(file_size) if file_size else "",
        "bitrate": format_data.get("bit_rate", ""),
    }
    
    return metadata


def generate_metadata_csv(video_files: List[str], output_csv: str) -> None:
    """
    Generate CSV file with metadata from all videos.
    
    Args:
        video_files: List of relative video file paths
        output_csv: Path to output CSV file
    """
    rclone_root = CONFIG.get("rclone_root")
    rclone_master_root = CONFIG.get("rclone_master_root") or rclone_root
    
    print(f"\n📝 Erstelle CSV-Datei: {output_csv}")
    print(f"📊 Analysiere {len(video_files)} Videos...")
    
    # List all DMG archives once at the beginning
    dmg_files = list_all_dmg_archives_on_server()
    
    with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=CSV_FIELDNAMES, extrasaction='ignore')
        writer.writeheader()
        
        for i, video_file in enumerate(video_files, 1):
            # Build full remote path using proper path joining
            # Remove leading slash from video_file if present
            video_file_clean = video_file.lstrip('/')
            remote_path = f"{rclone_root}/{video_file_clean}"
            
            print(f"[{i}/{len(video_files)}] {video_file}...", end=" ")
            
            # Extract metadata with automatic retry for large files
            metadata = extract_metadata_with_retry(remote_path)
            
            if "error" in metadata:
                print(f"❌")
                print(f"   ERROR: {metadata['error']}")
            else:
                print(f"✅")
                
                # Try to match DMG archive
                video_id = extract_video_id_from_comment(metadata.get("comment", ""))
                dmg_match = match_dmg_to_video(video_id, dmg_files)
                
                if dmg_match:
                    # Add full rclone path for DMG
                    metadata["dmg_filepath"] = f"{rclone_master_root}/{dmg_match}"
                else:
                    metadata["dmg_filepath"] = ""
            
            # Write to CSV
            writer.writerow(metadata)
    
    print(f"\n✅ CSV-Datei erstellt: {output_csv}")


def index_server_videos() -> Optional[str]:
    """
    Main function to index all videos on the server and generate CSV.
    
    Returns:
        Path to generated CSV file, or None if failed
    """
    print("\n" + "=" * 80)
    print("🎬 METADATA-INDEXIERUNG - Server-Videos")
    print("=" * 80)
    
    # List all videos
    video_files = list_all_videos_on_server()
    
    if not video_files:
        print("⚠️  Keine Videos gefunden")
        return None
    
    # Generate output filename
    work_dir = CONFIG.get("work_dir")
    if not work_dir:
        work_dir = os.path.expanduser("~/Movies/Internetfilme/Work")
    
    os.makedirs(work_dir, exist_ok=True)
    
    output_csv = os.path.join(work_dir, "video_metadata_index.csv")
    
    # Extract metadata and generate CSV
    generate_metadata_csv(video_files, output_csv)
    
    # Show summary
    print("\n" + "=" * 80)
    print("📋 ZUSAMMENFASSUNG")
    print("=" * 80)
    print(f"Analysierte Videos: {len(video_files)}")
    print(f"CSV-Datei:          {output_csv}")
    print(f"Speicherort:        {work_dir}")
    
    return output_csv


def update_csv_with_video(video_remote_path: str, dmg_remote_path: Optional[str] = None, csv_path: Optional[str] = None) -> bool:
    """
    Update or append a single video entry in the metadata CSV file.
    
    This function is used to keep the CSV index current after deploying a new video.
    It extracts metadata from the remote video and either updates an existing entry
    or appends a new one to the CSV.
    
    Args:
        video_remote_path: Full rclone path to the video file (e.g., "lyssach-nas:/Internetfilme/Patrick/Channel/video.m4v")
        dmg_remote_path: Optional full rclone path to the DMG archive (e.g., "lyssach-nas:/Internetfilme/2025/2025-01-15_youtube_abc123.dmg")
        csv_path: Path to the CSV file. If None, uses default work_dir location.
    
    Returns:
        True if successful, False otherwise
    """
    try:
        # Determine CSV path
        if csv_path is None:
            work_dir = CONFIG.get("work_dir")
            if not work_dir:
                work_dir = os.path.expanduser("~/Movies/Internetfilme/Work")
            os.makedirs(work_dir, exist_ok=True)
            csv_path = os.path.join(work_dir, "video_metadata_index.csv")
        
        # Extract metadata from the video
        print(f"📊 Extrahiere Metadaten von {os.path.basename(video_remote_path)}...")
        metadata = extract_metadata_with_retry(video_remote_path)
        
        if "error" in metadata:
            print(f"⚠️  Fehler beim Extrahieren der Metadaten: {metadata['error']}")
            return False
        
        # Add DMG filepath if provided, otherwise try to find it
        if dmg_remote_path:
            metadata["dmg_filepath"] = dmg_remote_path
        else:
            # Try to find matching DMG archive
            video_id = extract_video_id_from_comment(metadata.get("comment", ""))
            if video_id:
                dmg_files = list_all_dmg_archives_on_server()
                dmg_match = match_dmg_to_video(video_id, dmg_files)
                if dmg_match:
                    rclone_master_root = CONFIG.get("rclone_master_root") or CONFIG.get("rclone_root")
                    metadata["dmg_filepath"] = f"{rclone_master_root}/{dmg_match}"
                else:
                    metadata["dmg_filepath"] = ""
            else:
                metadata["dmg_filepath"] = ""
        
        # Check if CSV exists
        csv_exists = os.path.exists(csv_path)
        
        if csv_exists:
            # Read existing CSV to check if video already exists
            existing_rows = []
            video_exists = False
            
            with open(csv_path, 'r', newline='', encoding='utf-8') as csvfile:
                reader = csv.DictReader(csvfile)
                for row in reader:
                    # Check if this is the same video (by filepath)
                    if row.get('filepath') == metadata['filepath']:
                        # Update the existing entry
                        video_exists = True
                        existing_rows.append(metadata)
                    else:
                        existing_rows.append(row)
            
            # If video didn't exist, append it to the list
            if not video_exists:
                existing_rows.append(metadata)
            
            # Write back the CSV with all rows (updated or with new video appended)
            with open(csv_path, 'w', newline='', encoding='utf-8') as csvfile:
                writer = csv.DictWriter(csvfile, fieldnames=CSV_FIELDNAMES, extrasaction='ignore')
                writer.writeheader()
                writer.writerows(existing_rows)
            
            # Print appropriate success message
            if video_exists:
                print(f"✅ Video-Eintrag in CSV aktualisiert: {csv_path}")
            else:
                print(f"✅ Video-Eintrag zur CSV hinzugefügt: {csv_path}")
        else:
            # Create new CSV with this video
            with open(csv_path, 'w', newline='', encoding='utf-8') as csvfile:
                writer = csv.DictWriter(csvfile, fieldnames=CSV_FIELDNAMES, extrasaction='ignore')
                writer.writeheader()
                writer.writerow(metadata)
            print(f"✅ Neue CSV erstellt mit Video-Eintrag: {csv_path}")
        
        return True
    
    except Exception as e:
        print(f"⚠️  Fehler beim Aktualisieren der CSV: {str(e)}")
        return False


def search_dmg_archives(search_term: str, csv_path: Optional[str] = None, search_fields: Optional[List[str]] = None) -> List[Dict[str, Any]]:
    """
    Search for DMG archives by metadata (title, artist, album, etc.).
    
    This function searches the video metadata index CSV for entries matching
    the search term in the specified fields and returns entries that have
    an associated DMG archive.
    
    Args:
        search_term: Term to search for (case-insensitive)
        csv_path: Path to the CSV file. If None, uses default work_dir location.
        search_fields: List of field names to search in. If None, searches in
                      ['title', 'artist', 'album', 'description']
    
    Returns:
        List of matching video metadata dictionaries that have DMG archives
    
    Example:
        >>> results = search_dmg_archives("Starship")
        >>> for result in results:
        ...     print(f"Title: {result['title']}")
        ...     print(f"DMG: {result['dmg_filepath']}")
    """
    # Determine CSV path
    if csv_path is None:
        work_dir = CONFIG.get("work_dir")
        if not work_dir:
            work_dir = os.path.expanduser("~/Movies/Internetfilme/Work")
        csv_path = os.path.join(work_dir, "video_metadata_index.csv")
    
    # Check if CSV exists
    if not os.path.exists(csv_path):
        print(f"⚠️  CSV-Datei nicht gefunden: {csv_path}")
        return []
    
    # Default search fields
    if search_fields is None:
        search_fields = ['title', 'artist', 'album', 'description', 'long_description']
    
    # Normalize search term for case-insensitive search
    search_term_lower = search_term.lower()
    
    # Read CSV and search
    matching_rows = []
    
    try:
        with open(csv_path, 'r', newline='', encoding='utf-8') as csvfile:
            reader = csv.DictReader(csvfile)
            
            for row in reader:
                # Skip rows without DMG archives
                if not row.get('dmg_filepath'):
                    continue
                
                # Check if search term matches any of the search fields
                match_found = False
                for field in search_fields:
                    field_value = row.get(field, '')
                    if field_value and search_term_lower in field_value.lower():
                        match_found = True
                        break
                
                if match_found:
                    matching_rows.append(row)
        
        return matching_rows
    
    except Exception as e:
        print(f"⚠️  Fehler beim Durchsuchen der CSV: {str(e)}")
        return []
