import os
import time
import json
import shutil
import glob
from .config import CONFIG
from .utils import run_shell, save_state, check_disk_space

def verify_download_success(job_dir, require_thumbnail=True):
    """
    Überprüft, ob der Download erfolgreich war.
    Prüft auf das Vorhandensein von Video-Datei, Thumbnail und Metadaten.
    
    Args:
        job_dir: Pfad zum Job-Verzeichnis
        require_thumbnail: Ob Thumbnail zwingend erforderlich ist (Standard: True)
    
    Returns:
        bool: True wenn Download erfolgreich war, False sonst
    """
    if not os.path.exists(job_dir):
        return False
    
    # Prüfe auf Video-Datei (original.mkv, original.mp4, original.webm)
    video_extensions = ['.mkv', '.mp4', '.webm']
    audio_extensions = ['.m4a', '.mp3']  # Separate Liste für Audio-Dateien
    
    media_found = False
    for ext in video_extensions + audio_extensions:
        if os.path.exists(os.path.join(job_dir, f"original{ext}")):
            media_found = True
            break
    
    # Alternative: Suche nach beliebiger Medien-Datei mit "original" im Namen
    if not media_found:
        media_files = glob.glob(os.path.join(job_dir, "original.*"))
        # Filter nur Medien-Dateien (keine .json, .vtt, etc.)
        media_files = [f for f in media_files if not f.endswith(('.json', '.vtt', '.srt', '.txt'))]
        media_found = len(media_files) > 0
    
    # Prüfe auf Thumbnail (original.jpg, original.png, original.webp)
    thumbnail_extensions = ['.jpg', '.jpeg', '.png', '.webp']
    thumbnail_found = False
    for ext in thumbnail_extensions:
        if os.path.exists(os.path.join(job_dir, f"original{ext}")):
            thumbnail_found = True
            break
    
    # Prüfe auf Metadaten (original.info.json)
    metadata_found = os.path.exists(os.path.join(job_dir, "original.info.json"))
    
    # Download ist erfolgreich wenn Video vorhanden ist
    # Thumbnail ist optional (manche Videos haben keine Thumbnails, z.B. live streams)
    success = media_found and (thumbnail_found or not require_thumbnail)
    
    if not success:
        print(f"   ⚠️  Download-Verifikation fehlgeschlagen:")
        if not media_found:
            print(f"      - Video/Audio-Datei fehlt")
        if require_thumbnail and not thumbnail_found:
            print(f"      - Thumbnail fehlt (kann mit require_thumbnail=False deaktiviert werden)")
        if not metadata_found:
            print(f"      - Metadaten fehlen (optional)")
    
    return success

def is_video_unavailable(output):
    """Check if yt-dlp output indicates a video is permanently unavailable.
    
    These are non-recoverable errors where retrying would be pointless.
    
    Args:
        output: The captured stdout/stderr output from yt-dlp
    
    Returns:
        bool: True if the video is permanently unavailable
    """
    unavailable_patterns = [
        "Video unavailable",
        "This video has been removed",
        "This video is not available",
        "This video was removed",
        "This video is private",
        "Private video",
        "This video is no longer available",
        "has been removed due to",
        "removed due to a legal complaint",
    ]
    output_lower = output.lower()
    return any(pattern.lower() in output_lower for pattern in unavailable_patterns)


def load_archive_ids():
    """Lädt bereits heruntergeladene IDs aus dem Archiv."""
    archive_path = CONFIG.get("archive_file")
    ids = set()
    if os.path.exists(archive_path):
        try:
            with open(archive_path, 'r') as f:
                for line in f:
                    parts = line.strip().split()
                    if len(parts) >= 2:
                        # Format oft: 'youtube VIDEO_ID'
                        ids.add(parts[1])
        except: pass
    return ids

def fetch_video(url, target_name):
    # 0. Playlist Check (Vorab-Analyse)
    # Wir prüfen, ob es eine Playlist ist, um sie in Einzel-Jobs zu zerlegen.
    if "list=" in url and "watch?v=" not in url: # Reine Playlist URL (grobe Heuristik, yt-dlp check ist besser)
        is_playlist_likely = True
    elif "list=" in url: # Mix (Video in Playlist context) -> User will vllt nur das Video? 
        # Standardverhalten: Nur Video. User muss explizit Playlist-URL geben für alle.
        # Aber yt-dlp default ist: Video in Context -> Lädt Playlist. Wir wollen das kontrollieren.
        # Wir überlassen es yt-dlp zu entscheiden, was es ist, aber wir nutzen --flat-playlist um zu schauen.
        is_playlist_likely = True
    else:
        is_playlist_likely = False

    # Check mit yt-dlp (Flat Dump)
    try:
        # Hole Metadaten OHNE Download
        # --flat-playlist: Listet Videos auf ohne sie zu prüfen/laden
        cmd_dump = f"{CONFIG['ytdlp']} -J --flat-playlist '{url}' --cookies-from-browser safari"
        dump_output = run_shell(cmd_dump, return_output=True)
        meta = json.loads(dump_output)
        
        if meta.get("_type") == "playlist" and "entries" in meta:
            entries = meta.get("entries", [])
            print(f"📋 Playlist erkannt: '{meta.get('title')}' mit {len(entries)} Einträgen.")
            
            # Archiv prüfen
            archived_ids = load_archive_ids()
            new_urls = []
            
            for entry in entries:
                vid_id = entry.get("id")
                # Check ob ID im Archiv
                if vid_id in archived_ids:
                    continue
                
                # Wenn nicht im Archiv, zur Liste hinzufügen
                # Baue URL (yt-dlp liefert meist id)
                if entry.get("url"):
                    new_urls.append(entry.get("url"))
                else:
                    new_urls.append(f"https://www.youtube.com/watch?v={vid_id}")
            
            print(f"   ℹ️  Davon neu (nicht im Archiv): {len(new_urls)}")
            
            if not new_urls:
                print("   ✅ Keine neuen Videos. Alles erledigt.")
                return "playlist_skipped"

            # Rekursiver Abruf für jedes neue Video
            # Wir nutzen hier eine lokale Schleife mit Pause
            successful_downloads = 0
            failed_downloads = 0
            skipped_unavailable = 0
            downloaded_job_paths = []  # Collect job paths for later processing
            
            for i, vid_url in enumerate(new_urls, 1):
                print(f"\n   🔄 [Playlist {i}/{len(new_urls)}] Starte Einzel-Download...")
                result = fetch_video(vid_url, target_name) # Rekursion (aber jetzt Einzelvideo)
                
                # Check if download was successful
                if result is None:
                    # Download failed
                    failed_downloads += 1
                    print(f"\n   ❌ Playlist-Download gestoppt wegen Fehler.")
                    print(f"      Erfolgreich: {successful_downloads}, Fehlgeschlagen: {failed_downloads}, Übersprungen: {skipped_unavailable}")
                    print(f"      Verbleibende Videos: {len(new_urls) - i}")
                    break
                elif result == "video_unavailable":
                    # Video is permanently unavailable - skip it, don't stop the playlist
                    skipped_unavailable += 1
                    print(f"   ⏭️  Video übersprungen (nicht verfügbar). Fahre mit nächstem fort.")
                elif isinstance(result, str):
                    # Single video successfully downloaded, result is a job directory path
                    # Validate that the job path is a valid directory before collecting
                    if os.path.isdir(result):
                        successful_downloads += 1
                        downloaded_job_paths.append(result)
                    else:
                        print(f"\n   ⚠️  Warnung: Job-Pfad ist kein gültiges Verzeichnis: {result}")
                        failed_downloads += 1
                else:
                    # Unexpected result type (shouldn't happen in recursive call, but handle gracefully)
                    # This represents an internal programming error, not a user-facing download failure
                    # We don't increment failed_downloads to avoid misleading statistics
                    print(f"\n   ⚠️  Interner Fehler: Unerwarteter Rückgabewert vom Download: {type(result)}")
                
                # Pause (Modulvariable simulieren oder hardcoden)
                if i < len(new_urls):
                    wait_sec = CONFIG.get("wait_time_playlist_sec", 5)
                    print(f"   ⏳ Warte {wait_sec}s (Playlist-Rate-Limit)...")
                    try:
                        time.sleep(wait_sec)
                    except KeyboardInterrupt:
                        print("   ⚠️ Pause übersprungen.")
            
            # Summary for playlist
            if failed_downloads > 0:
                print(f"\n   ⚠️  Playlist-Download wurde wegen Fehler gestoppt.")
                print(f"      Erfolgreich: {successful_downloads}, Fehlgeschlagen: {failed_downloads}, Übersprungen: {skipped_unavailable}")
                return None
            else:
                print(f"\n   ✅ Playlist-Download abgeschlossen.")
                print(f"      Erfolgreich: {successful_downloads}, Fehlgeschlagen: {failed_downloads}, Übersprungen: {skipped_unavailable}")
                # Return a dictionary with playlist info and job paths
                return {
                    "type": "playlist_completed",
                    "job_paths": downloaded_job_paths,
                    "successful": successful_downloads,
                    "failed": failed_downloads,
                    "skipped_unavailable": skipped_unavailable
                }

    except Exception as e:
        # Fallback: Wenn Dump fehlschlägt, ist es vielleicht ein Einzelvideo oder Fehler
        # Wir machen einfach weiter mit dem normalen Flow unten
        pass

    # 1. Speicher Check
    if not check_disk_space(30):
        print("❌ Download abgebrochen: Zu wenig Speicherplatz.")
        return None

    # 2. Job ID generieren
    job_id = f"job_{int(time.time())}"
    work_dir = CONFIG.get("work_dir")
    job_dir = os.path.join(work_dir, job_id)
    os.makedirs(job_dir, exist_ok=True)
    
    print(f"🚀 Starte Download für Ziel: {target_name}")
    print(f"   Job-Ordner: {job_dir}")
    
    # 3. State speichern
    save_state(job_dir, {
        "url": url, 
        "target": target_name, 
        "status": "downloading"
    })
    
    # 4. yt-dlp Befehl
    # FIX: Wir nutzen --embed-subs, aber KEIN --write-subs.
    # Damit bleiben die Subs im Container und SponsorBlock schneidet alles stabil in einem Rutsch.
    
    # Sponsorblock-Einstellungen basierend auf Video-Typ
    # Musikvideos: Behalten Intros/Outros (oft Teil der künstlerischen Gestaltung)
    # Andere Videos: Entfernen auch Intros (meist weniger relevant)
    music_target = CONFIG.get("music_target", "Musikvideos")
    is_music_video = (target_name == music_target)
    
    if is_music_video:
        sponsorblock_categories = "sponsor,selfpromo,interaction"
    else:
        sponsorblock_categories = "sponsor,intro,selfpromo,interaction"
    
    ytdlp_min_sleep = CONFIG.get("wait_time_ytdlp_min_sec", 5)
    ytdlp_max_sleep = CONFIG.get("wait_time_ytdlp_max_sec", 10)
    
    def _build_ytdlp_command(use_archive=True):
        """Builds the yt-dlp command with or without archive."""
        archive_flag = f"--download-archive '{CONFIG['archive_file']}' " if use_archive else ""
        return (
            f"{CONFIG['ytdlp']} '{url}' "
            f"-f 'bv*+ba/best' --merge-output-format mkv "
            f"{archive_flag}"
            # WICHTIG: --no-playlist verhindern, dass er bei Einzel-Links, die Teil einer Playlist sind,
            # doch wieder die ganze Liste lädt (wir haben das oben ja schon behandelt)
            f"--no-playlist " 
            f"--write-info-json "
            f"--write-thumbnail " 
            f"--write-auto-subs " 
            f"--sub-lang 'en,en-orig,de,de-orig' " 
            f"--convert-subs srt " # Fix für Timestamps in Untertiteln: Konvertierung zu SRT entfernt Karaoke-Tags
            f"--embed-subs " # Subs direkt ins MKV
            f"--embed-chapters "
            f"--cookies-from-browser safari " 
            f"--sponsorblock-remove '{sponsorblock_categories}' "
            # WICHTIG: Pause für Playlisten (yt-dlp interne Queue) - hier irrelevant da wir no-playlist nutzen, aber schadet nicht
            f"--sleep-interval {ytdlp_min_sleep} --max-sleep-interval {ytdlp_max_sleep} "
            f"--sleep-subtitles {ytdlp_min_sleep} " # Wartezeit zwischen Untertitel-Downloads gegen 429 errors
            f"--progress-delta 10 "  # Zeige Download-Progress nur alle 10 Sekunden
            f"-o '{job_dir}/original.%(ext)s'"
        )
    
    # First attempt: download with archive check
    cmd = _build_ytdlp_command(use_archive=True)
    
    # Retry-Konfiguration
    retry_enabled = CONFIG.get("retry_on_failure", False)
    retry_wait_minutes = CONFIG.get("retry_wait_minutes", 30)
    max_attempts = CONFIG.get("max_retries", 3)  # max_retries in config means max total attempts
    
    attempt = 1  # Start at 1 for clearer messaging (attempt 1, 2, 3...)
    last_error = None
    
    # Try up to max_attempts times
    while attempt <= max_attempts:
        try:
            result = run_shell(cmd, stream_output=True)
            output = result.get("stdout", "")
            returncode = result.get("returncode", 0)
            
            # Check if video was skipped because it's already in archive
            # Note: yt-dlp returns exit code 0 when skipping archived videos, so we must check the output message.
            # This is the standard detection method as yt-dlp doesn't provide a specific exit code for this case.
            if "has already been recorded in the archive" in output:
                print("\n⚠️  Dieses Video wurde bereits heruntergeladen.")
                choice = input("   Möchtest du es dennoch erneut herunterladen? [y/N]: ").strip().lower()
                
                if choice in ['y', 'j', 'ja']:
                    print("   🔄 Starte erzwungenen Download (ohne Archive-Check)...")
                    # Retry without archive flag
                    cmd_no_archive = _build_ytdlp_command(use_archive=False)
                    result_retry = run_shell(cmd_no_archive, stream_output=True)
                    output_retry = result_retry.get("stdout", "")
                    returncode_retry = result_retry.get("returncode", 0)
                    
                    if returncode_retry != 0:
                        raise Exception(f"yt-dlp exited with code {returncode_retry}")
                    
                    # Verify download success
                    require_thumb = CONFIG.get("require_thumbnail", True)
                    if not verify_download_success(job_dir, require_thumbnail=require_thumb):
                        raise Exception("Download-Verifikation fehlgeschlagen: Video oder Thumbnail fehlt")
                    
                    # Success!
                    print("   ✅ Download abgeschlossen und erfolgreich verifiziert.")
                    save_state(job_dir, {"status": "fetched", "target": target_name})
                    return job_dir
                else:
                    print("   ℹ️  Download übersprungen.")
                    # Clean up the empty job directory
                    try:
                        shutil.rmtree(job_dir)
                    except Exception as cleanup_error:
                        print(f"   ⚠️  Warnung: Konnte Job-Ordner nicht löschen: {cleanup_error}")
                    return None
            elif returncode != 0:
                # Check for permanent unavailability before retrying
                if is_video_unavailable(output):
                    print(f"\n⚠️  Video nicht verfügbar (wird übersprungen): {url}")
                    try:
                        shutil.rmtree(job_dir)
                    except Exception as cleanup_error:
                        print(f"   ⚠️  Warnung: Konnte Job-Ordner nicht löschen: {cleanup_error}")
                    return "video_unavailable"
                raise Exception(f"yt-dlp exited with code {returncode}")
            
            # Verify download success for all downloads
            require_thumb = CONFIG.get("require_thumbnail", True)
            if not verify_download_success(job_dir, require_thumbnail=require_thumb):
                raise Exception("Download-Verifikation fehlgeschlagen: Video oder Thumbnail fehlt")
            
            # Success!
            print("   ✅ Download erfolgreich verifiziert (Video + Thumbnail vorhanden)")
            save_state(job_dir, {"status": "fetched", "target": target_name})
            return job_dir
            
        except Exception as e:
            last_error = e
            
            print(f"❌ Download fehlgeschlagen (Versuch {attempt}/{max_attempts}): {e}")
            
            # Check if we should retry
            if retry_enabled and attempt < max_attempts:
                print(f"\n🔄 Erneuter Versuch wird gestartet ({attempt + 1}/{max_attempts}).")
                print(f"   Warte {retry_wait_minutes} Minuten vor erneutem Versuch...")
                print(f"   (Drücke Ctrl+C um abzubrechen)")
                
                try:
                    # Wait for configured time
                    time.sleep(retry_wait_minutes * 60)
                    print(f"\n   🔄 Starte erneuten Download-Versuch...")
                    attempt += 1
                    # Keep job_dir for retry, don't clean up yet
                    continue
                except KeyboardInterrupt:
                    print("\n   ⚠️  Retry abgebrochen durch Benutzer.")
                    # Clean up and return failure
                    break
            else:
                # No retry or max attempts reached
                if retry_enabled and attempt >= max_attempts:
                    print(f"\n   ⚠️  Maximale Anzahl von Versuchen ({max_attempts}) erreicht.")
                break
    
    # If we get here, all retries failed
    print(f"\n❌ Download endgültig fehlgeschlagen: {last_error}")
    # Clean up the job directory on final failure
    try:
        shutil.rmtree(job_dir)
    except Exception as cleanup_error:
        print(f"   ⚠️  Warnung: Konnte Job-Ordner nicht löschen: {cleanup_error}")
    return None