import json
import re
import os
import time
import traceback
import random
import anthropic
from .config import CONFIG, get_model_name
from integrator.module_subtitles import parse_vtt_text
from .genre_classifier import get_genre_rules_prompt, get_validated_genre

# --- CONTROLLED VOCABULARY & KEYWORD FILTERING ---

_MASTERLIST_KEYWORDS = None  # Cache for loaded keywords
_VOCAB_SAMPLE_SIZE = 150  # Number of keywords to include in prompt (for token efficiency)

def _load_masterlist_keywords():
    """
    Lädt die Masterlist Keywords aus der Datei.
    Wird beim ersten Aufruf geladen und gecacht.
    """
    global _MASTERLIST_KEYWORDS
    
    if _MASTERLIST_KEYWORDS is not None:
        return _MASTERLIST_KEYWORDS
    
    # Suche nach Masterlist_keywords.txt im Repository Root
    # Versuche zuerst relative zum aktuellen File, dann Fallbacks
    current_dir = os.path.dirname(os.path.abspath(__file__))
    
    # Mögliche Pfade in Prioritätsreihenfolge
    possible_paths = [
        os.path.abspath(os.path.join(current_dir, "..", "..", "Masterlist_keywords.txt")),
        os.path.join(os.getcwd(), "Masterlist_keywords.txt"),
        os.path.join(os.path.expanduser("~"), ".config", "internetfilm_manager", "Masterlist_keywords.txt")
    ]
    
    keywords = []
    masterlist_path = None
    
    # Finde erste existierende Datei
    for path in possible_paths:
        if os.path.exists(path):
            masterlist_path = path
            break
    
    if masterlist_path:
        try:
            with open(masterlist_path, 'r', encoding='utf-8') as f:
                for line in f:
                    line = line.strip()
                    # Ignoriere Kommentare und leere Zeilen
                    if line and not line.startswith('#') and not line.startswith('==='):
                        keywords.append(line)
            print(f"   📚 Kontrolliertes Vokabular geladen: {len(keywords)} Keywords aus {masterlist_path}")
        except Exception as e:
            print(f"   ⚠️  Fehler beim Laden der Masterlist: {e}")
    else:
        print(f"   ℹ️  Keine Masterlist_keywords.txt gefunden.")
        print(f"   Gesuchte Pfade:")
        for path in possible_paths:
            print(f"     - {path}")
    
    _MASTERLIST_KEYWORDS = keywords
    return keywords

def _get_keyword_blacklist():
    """
    Gibt eine Liste von zu filternden Keywords zurück.
    Diese werden nach der AI-Extraktion entfernt.
    """
    return [
        # Qualitätsangaben
        "4K", "HD", "UHD", "1080p", "720p", "480p", "8K",
        "High Definition", "Ultra HD", "Full HD",
        # Technische Begriffe
        "Video", "Official Video", "Offiziell", "Official",
        "Remastered", "Remix", "Extended", "Director's Cut",
        "Lyrics", "Untertitel", "Subtitle",
        # Generische Begriffe
        "Content", "Digital", "Online", "Youtube", "Vlog",
        # Diese werden oft fälschlicherweise als Keywords erkannt
        "Link", "Beschreibung", "Info", "More", "Channel",
    ]

def _clean_and_deduplicate_tags(tags_string):
    """
    Bereinigt und dedupliziert Keywords aus einem String.
    
    Args:
        tags_string: Komma-getrennte Keywords, JSON-Array-String oder Liste
    
    Returns:
        Bereinigte, deduplizierte Komma-getrennte Keywords
    """
    if not tags_string:
        return ""
    
    # Handling verschiedener Input-Typen
    tags_list = []
    
    # Wenn Input bereits eine Liste ist, direkt verwenden
    if isinstance(tags_string, list):
        tags_list = [str(tag).strip() for tag in tags_string]
    # Wenn String, versuche JSON zu parsen oder comma-split
    elif isinstance(tags_string, str):
        try:
            parsed = json.loads(tags_string)
            if isinstance(parsed, list):
                tags_list = [str(tag).strip() for tag in parsed]
            elif isinstance(parsed, str):
                # Einzelner String aus JSON
                tags_list = [parsed.strip()]
            elif isinstance(parsed, (int, float, bool)):
                # Primitive Typen als String
                tags_list = [str(parsed).strip()]
            else:
                # Unerwarteter Typ (dict, nested structures) - logge Warning und nutze Fallback
                print(f"   ⚠️  Unerwarteter JSON-Typ für Tags: {type(parsed).__name__}")
                # Fallback: Split original string by comma
                tags_list = [tag.strip() for tag in str(tags_string).split(',')]
        except (json.JSONDecodeError, TypeError):
            # Fallback: Split by comma
            tags_list = [tag.strip() for tag in str(tags_string).split(',')]
    else:
        # Andere Typen: Zu String konvertieren und splitten
        tags_list = [tag.strip() for tag in str(tags_string).split(',')]
    
    # Blacklist laden
    blacklist = _get_keyword_blacklist()
    blacklist_lower = [b.lower() for b in blacklist]
    
    # Filtern und deduplizieren (case-insensitive)
    seen = set()
    cleaned_tags = []
    
    for tag in tags_list:
        if not tag:
            continue
        
        # Entferne zusätzliche Leerzeichen
        tag = ' '.join(tag.split())
        
        # Prüfe Blacklist
        if tag.lower() in blacklist_lower:
            continue
        
        # Deduplizierung (case-insensitive)
        tag_lower = tag.lower()
        if tag_lower not in seen:
            seen.add(tag_lower)
            cleaned_tags.append(tag)
    
    return ", ".join(cleaned_tags)

def _is_retryable_error(exception):
    """
    Check if an exception is a retryable error (e.g., 529 overloaded).
    """
    error_str = str(exception)
    # Check for overloaded or rate limit errors
    if '529' in error_str or 'overloaded' in error_str.lower():
        return True
    if '429' in error_str or 'rate' in error_str.lower():
        return True
    # Check for other transient errors
    if '503' in error_str or 'UNAVAILABLE' in error_str:
        return True
    if isinstance(exception, anthropic.APIStatusError):
        if exception.status_code in (429, 503, 529):
            return True
    return False

def _json_string_literal(value):
    """
    Returns a JSON-safe string literal for prompt interpolation.
    """
    return json.dumps("" if value is None else str(value), ensure_ascii=False)

def _repair_unescaped_quotes_in_json(text):
    """
    Repairs common Claude JSON issues where quotes inside string values are not escaped.
    Only quotes that are clearly not terminating a JSON string are escaped.
    """
    repaired = []
    in_string = False
    escaped = False
    
    for index, char in enumerate(text):
        if char == '"' and not escaped:
            if not in_string:
                in_string = True
                repaired.append(char)
                continue
            
            next_index = index + 1
            while next_index < len(text) and text[next_index].isspace():
                next_index += 1
            
            next_char = text[next_index] if next_index < len(text) else None
            if next_char is None or next_char in {",", "}", "]", ":"}:
                in_string = False
                repaired.append(char)
            else:
                repaired.append('\\"')
            escaped = False
            continue
        
        repaired.append(char)
        if in_string and char == "\\" and not escaped:
            escaped = True
        else:
            escaped = False
    
    return "".join(repaired)

def _load_json_with_repair(text):
    """
    Loads JSON, retrying once with repaired inner quotes if necessary.
    """
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        repaired_text = _repair_unescaped_quotes_in_json(text)
        if repaired_text != text:
            return json.loads(repaired_text)
        raise

def _get_json_response(client, model_name, prompt, max_retries=2, max_tokens=4096, system=None):
    """
    Basis-Funktion für JSON-Antworten von Claude mit Retry-Logik.
    Extrahiert JSON robust aus Markdown oder Text.
    
    Args:
        client: Anthropic client instance
        model_name: Name of the model to use
        prompt: The prompt to send (string or list of content blocks for vision)
        max_retries: Maximum number of retry attempts for transient errors (default: 2)
        max_tokens: Maximum tokens for the response (default: 4096)
        system: Optional system message for prompt caching (string or list of content blocks)
    
    Returns:
        Parsed JSON response or None on failure
    """
    for attempt in range(max_retries + 1):  # +1 for initial attempt
        text = None  # Initialize to avoid NameError in exception handler
        try:
            if attempt > 0:
                # Wait before retrying (exponential backoff: 2s, 4s)
                wait_time = 2 ** attempt
                print(f"   🔄 Retry {attempt}/{max_retries}: Warte {wait_time}s vor erneutem Versuch...")
                time.sleep(wait_time)
                print(f"   ⏳ Sende Anfrage an Claude API...")
            
            # Build message content
            if isinstance(prompt, list):
                # Multi-part content (e.g., image + text for vision)
                content = prompt
            else:
                content = prompt
            
            # Build kwargs for API call
            kwargs = {
                "model": model_name,
                "max_tokens": max_tokens,
                "messages": [{"role": "user", "content": content}]
            }
            
            # Add system message with prompt caching support if provided
            if system:
                # Enable caching for system prompt using cache_control
                # Anthropic SDK 0.79.0 supports cache_control
                if isinstance(system, str):
                    kwargs["system"] = [
                        {
                            "type": "text",
                            "text": system,
                            "cache_control": {"type": "ephemeral"}
                        }
                    ]
                else:
                    # system is already a list of content blocks
                    kwargs["system"] = system
            
            response = client.messages.create(**kwargs)
            
            # Success feedback for retries
            if attempt > 0:
                print(f"   ✅ Claude API hat geantwortet nach Retry {attempt}/{max_retries}")
            
            # Extract text from response
            if not response.content:
                print("⚠️  Keine Antwort vom Modell erhalten.")
                return None
            
            text = response.content[0].text.strip()
            
            # 1. Versuche, JSON direkt zu parsen
            try:
                return _load_json_with_repair(text)
            except json.JSONDecodeError:
                pass # Weiter zum Regex
                
            # 2. Suche nach JSON Block ```json ... ``` oder ``` ... ```
            match = re.search(r"```(?:json)?\s*(.*?)```", text, re.DOTALL)
            if match:
                json_str = match.group(1).strip()
                return _load_json_with_repair(json_str)
                
            # 3. Suche nach dem ersten '{' und dem letzten '}' (oder '[' ']')
            # Fallback für rohes JSON im Text
            start_brace = text.find('{')
            start_bracket = text.find('[')
            
            start = -1
            end = -1
            
            if start_brace != -1 and (start_bracket == -1 or start_brace < start_bracket):
                start = start_brace
                end = text.rfind('}') + 1
            elif start_bracket != -1:
                start = start_bracket
                end = text.rfind(']') + 1
                
            if start != -1 and end != -1:
                json_str = text[start:end]
                return _load_json_with_repair(json_str)
                
            print(f"⚠️  Claude Antwort konnte nicht als JSON geparst werden.")
            print(f"--- RAW RESPONSE START ---\n{text}\n--- RAW RESPONSE END ---")
            return None
            
        except Exception as e:
            # Check if this is a retryable error
            if _is_retryable_error(e) and attempt < max_retries:
                print(f"⚠️  Claude API überlastet oder nicht verfügbar: {e}")
                print(f"   🔄 Versuche erneut ({attempt + 1}/{max_retries})...")
                continue  # Retry
            
            # Not retryable or max retries reached
            if _is_retryable_error(e) and attempt == max_retries:
                print(f"❌ Claude API Fehler: Maximale Anzahl von Retries ({max_retries}) erreicht.")
                print(f"   Letzter Fehler: {e}")
                print(f"   💡 Mögliche Lösungen:")
                print(f"      - In ein paar Minuten erneut versuchen")
                print(f"      - Anthropic API Status prüfen: https://status.anthropic.com/")
                print(f"      - API-Key und Quota überprüfen")
                # Raise the exception to allow calling code to handle the failure
                raise
            
            # For non-retryable errors
            print(f"⚠️  Claude API Fehler (nicht wiederholbar): {e}")
            if text is not None:
                # Provide context from response text if available
                print(f"--- RAW TEXT (Error context) ---\n{text[:500]}\n...")
            
            # For non-retryable errors, just return None
            return None

# --- LOGIK FÜR INTERNET-FILME (Vlogs, Dokus, etc.) ---

def _build_internet_video_system_prompt(vocab_context="", genre_rules=""):
    """
    Baut den statischen System-Prompt für Internet-Video-Analyse.
    Dieser Teil ist bei jedem Video identisch und wird von Anthropic gecacht.
    
    Args:
        vocab_context: Kontrolliertes Vokabular (wird gecacht, da Random-Seed fix ist)
        genre_rules: Genre-Klassifizierungsregeln
    
    Returns:
        System prompt string für prompt caching
    """
    return f"""Du bist ein professioneller Video-Archivar und internationaler Bibliothekar für ein langfristiges Medienarchiv. Deine Aufgabe ist die konsistente und hochwertige Kuratierung von Videos.

AUFGABE TITELBEREINIGUNG:
REGELN FÜR SONDERZEICHEN:
- ENTFERNE " | " (Pipe) und ersetze sie durch ": " oder " - " oder nutze Klammern (), je nach Kontext.
- Keine Emojis, keine Hashtags #.
- Entferne "Official Video", "4K", "HD" etc.

REGELN FÜR GROSS-/KLEINSCHREIBUNG (STRENG!):
- KEINE Wörter in NUR GROSSBUCHSTABEN (außer gängige Abkürzungen wie "USA", "NASA", "KI", "AI").
- ENGLISCH: Nutze "Title Case".
- DEUTSCH: Nutze normale Rechtschreibung (Nomen groß, Rest klein). KEIN Title Case!
- ALLE ANDEREN SPRACHEN: Nutze "Sentence case".

{genre_rules}

KEYWORDS (Tags) - KRITISCHE ARCHIVARISCHE REGELN:
a) PRIMÄR: Wähle thematische Schlagworte AUS DER REFERENZLISTE (kontrolliertes Vokabular).
b) NUR BEI BEDARF: Wenn kein passender Begriff existiert, generiere ein neues Keyword.
c) REGELN FÜR NEUE KEYWORDS:
   - Substantiv, Singular
   - Übergeordnete Kategorie statt spezifische Markennamen
   - Thematisch, nicht deskriptiv
d) VERBOTEN:
   - Kanalnamen
   - Technische Angaben ("4K", "HD", "Video", "Official")
   - Qualitätsangaben ("Remastered", "Director's Cut")
   - Dateiendungen oder Formate

BESCHREIBUNGEN - FORMATIERUNGSREGELN:
- Schreibe AUSSCHLIESSLICH in Prosatext (Fließtext).
- KEINE Aufzählungen, KEINE Bullet Points, KEINE Listen.
- KEINE Zeilenumbrüche innerhalb der Beschreibungen.
- Beschreibungen müssen ein durchgehender Text sein.
- ENTFERNE ALLE Hinweise auf Webinare, Anmeldungen, Links, externe Registrierungen.
- Konzentriere dich NUR auf den Videoinhalt selbst.
{vocab_context}"""

def _sample_transcript_intelligently(transcript_text, total_limit=8000):
    """
    Samples transcript intelligently from beginning, middle, and end.
    
    Args:
        transcript_text: Full transcript text
        total_limit: Total character limit (default: 8000)
    
    Returns:
        Sampled transcript with beginning (~3000), middle (~2500), and end (~2500) sections
    """
    if not transcript_text or len(transcript_text) <= total_limit:
        return transcript_text
    
    # Calculate section sizes proportionally (37.5%, 31.25%, 31.25%)
    beginning_size = int(total_limit * 0.375)
    middle_size = int(total_limit * 0.3125)
    end_size = int(total_limit * 0.3125)
    
    # Extract beginning
    beginning = transcript_text[:beginning_size]
    
    # Calculate middle position
    middle_start = len(transcript_text) // 2 - middle_size // 2
    middle_end = middle_start + middle_size
    middle = transcript_text[middle_start:middle_end]
    
    # Extract end
    end = transcript_text[-end_size:]
    
    # Combine with separators
    sampled = f"{beginning}\n\n[... Mittelteil des Transkripts ...]\n\n{middle}\n\n[... Weiterer Teil des Transkripts ...]\n\n{end}"
    
    return sampled

def analyze_internet_video(title, channel, description, vtt_path, known_lang_name="English", tags=None, video_path=None, video_url=None):
    """
    Erstellt Titel-Variationen, Beschreibungen und Genre für Standard-Videos.
    (Ehemals 'generate_metadata')
    
    Args:
        title: Original video title from platform
        channel: Channel name or uploader name
        description: Original video description from platform
        vtt_path: Path to VTT subtitle file for transcript analysis
        known_lang_name: Detected language name (e.g., "English", "German")
        tags: Optional list or comma-separated string of tags from info.json
        video_path: Optional path to video file (unused, kept for API compatibility)
        video_url: Optional video URL (unused, kept for API compatibility)
    """
    api_key = CONFIG.get("claude_key")
    if not api_key: return None
    
    transcript_text = parse_vtt_text(vtt_path, with_timestamps=False)
    
    client = anthropic.Anthropic(api_key=api_key)
    model_name = CONFIG.get("claude_model", "claude-sonnet-4-20250514")

    print(f"   🤖 [2/2] Generiere Metadaten ({known_lang_name})...")
    
    # Load controlled vocabulary
    masterlist = _load_masterlist_keywords()
    
    # Prepare controlled vocabulary context
    vocab_context = ""
    vocab_instruction = ""
    if masterlist:
        # Stratifizierte Stichprobe für bessere Themenabdeckung
        if len(masterlist) > _VOCAB_SAMPLE_SIZE:
            # Erste 60% der Sample-Größe aus Anfang (wichtige Basis-Keywords)
            base_size = int(_VOCAB_SAMPLE_SIZE * 0.6)
            vocab_sample_list = masterlist[:base_size]
            
            # Restliche 40% zufällig aus dem Rest samplen (diverse Themen)
            remaining_size = _VOCAB_SAMPLE_SIZE - base_size
            remaining_keywords = masterlist[base_size:]
            if len(remaining_keywords) > remaining_size:
                vocab_sample_list += random.Random(42).sample(remaining_keywords, remaining_size)
            else:
                vocab_sample_list += remaining_keywords
        else:
            vocab_sample_list = masterlist
        
        vocab_sample = ", ".join(vocab_sample_list)
        vocab_context = f"\n\n=== KONTROLLIERTES VOKABULAR (Referenzliste) ===\nNutze primär Begriffe aus dieser Liste: {vocab_sample}..."
        vocab_instruction = " Nutze PRIMÄR Begriffe aus dem kontrollierten Vokabular. Nur wenn KEIN passender Begriff existiert, darfst du ein neues Keyword generieren."
    
    # Prepare tags context
    tags_context = ""
    tags_instruction = ""
    if tags:
        # Convert tags to string, handling various input types
        if isinstance(tags, list) and tags:
            tags_str = ", ".join(str(tag) for tag in tags)
        elif isinstance(tags, str) and tags.strip():
            tags_str = tags
        else:
            tags_str = None
        
        if tags_str:
            tags_context = f"\nVorhandene Tags aus Metadaten: {_json_string_literal(tags_str)}"
            tags_instruction = " Berücksichtige dabei die vorhandenen Tags aus den Metadaten als Inspiration."
    
    # Prepare transcript info (Claude arbeitet ausschliesslich mit übergebenen Daten)
    transcript_info = ""
    transcript_instruction = ""
    if transcript_text and len(transcript_text) >= 100:
        # Use intelligent sampling: beginning + middle + end (~8000 chars total)
        sampled_transcript = _sample_transcript_intelligently(transcript_text, total_limit=8000)
        transcript_info = f"Transkript: {_json_string_literal(sampled_transcript)}"
        transcript_instruction = "- Untertitel können Namen FALSCH übersetzen oder transkribieren."
    else:
        transcript_info = "Transkript: (Kein Transkript verfügbar)"
        transcript_instruction = "- Kein Transkript vorhanden. Nutze Titel, Beschreibung und Tags für die Analyse."

    # Get genre classification rules
    genre_rules = get_genre_rules_prompt()
    
    # Build system message (static, will be cached)
    system_message = _build_internet_video_system_prompt(vocab_context, genre_rules)
    
    # Build user message (video-specific, dynamic)
    user_prompt = f"""INPUT:
Original-Titel: {_json_string_literal(title)}
Kanal/Creator: {_json_string_literal(channel)}
Sprache: {known_lang_name}
Original-Beschreibung: {_json_string_literal((description or '')[:1500])}
{transcript_info}{tags_context}

WICHTIG: Der Kanalname {_json_string_literal(channel)} enthält oft den korrekten Namen des Sprechers/Creators. Beachte dies bei allen Aufgaben.

AUFGABE 1: TITELBEREINIGUNG
Bereinige den Originaltitel in der ORIGINALSPRACHE und erstelle 3 Variationen.
- Wandle "PLURIBUS" in "Pluribus", "MINECRAFT" in "Minecraft" etc.
- Erstelle 3 Variationen in der ORIGINALSPRACHE.
- Keine Emojis, kein Clickbait.

AUFGABE 2: GENRE UND VERSCHLAGWORTUNG (TRICHTER-PRINZIP)
1. Bestimme das Hauptgenre basierend auf den Regeln.
2. Wähle Keywords (Tags) primär aus dem kontrollierten Vokabular.{vocab_instruction}{tags_instruction}
   - BEISPIELE für Regeln:
     * FALSCH: "Tata Signa 4018" → RICHTIG: "LKW"
     * FALSCH: "Nicole" (Kanalname) → RICHTIG: "Finanzen" (Thema)
     * FALSCH: "4K Video" → RICHTIG: (entfernen, kein Keyword)
     * FALSCH: "CS TOY" (Kanalname) → RICHTIG: "Spielzeug", "Traktor"
3. KURZBESCHREIBUNG: Max 250 Zeichen, 1-2 Sätze. Teaser.
4. LANGBESCHREIBUNG: Ausführliche Zusammenfassung, sachlich.

WICHTIGE REGEL FÜR NAMEN:
- Der Kanalname {_json_string_literal(channel)} enthält oft den KORREKTEN Namen des Sprechers/Creators.
{transcript_instruction}
- Überprüfe alle Personennamen im Transkript gegen den Kanalnamen.
- Verwende IMMER den Namen aus dem Kanal, wenn dieser eine Person ist.
- Beispiel: Wenn Kanal "Sam Ovens" heißt, aber Untertitel "Sam Evans" sagen → nutze "Sam Ovens".

WICHTIGE REGEL FÜR BESCHREIBUNGEN:
- Verwende den korrekten Namen aus dem Kanal {_json_string_literal(channel)} für den Sprecher/Creator.
- ENTFERNE ALLE Hinweise auf Webinare, Anmeldungen, Links, externe Registrierungen oder Online-Inhalte.
- Die Beschreibungen sind für ein privates Medienarchiv ohne Internet-Zugang.
- Konzentriere dich NUR auf den Videoinhalt selbst.

FORMAT (JSON):
{{
    "clean_original_title": "Bereinigter Titel",
    "title_variations": ["Var 1", "Var 2", "Var 3"],
    "genre": "Hauptgenre",
    "tags": ["Tag1", "Tag2", "Tag3"],
    "description_short": "Teaser...",
    "description_long": "Text..."
}}
"""
    
    combined_data = _get_json_response(client, model_name, user_prompt, max_tokens=2048, system=system_message)
    if not combined_data:
        print("   ❌ Schritt [2/2] fehlgeschlagen: Claude API konnte keine Metadaten generieren.")
        print("   💡 Auto-Kuratierung kann nicht fortgesetzt werden.")
        return None
    
    # Extract clean title
    clean_title = combined_data.get("clean_original_title", title)
    
    # Extract and clean tags
    raw_tags = combined_data.get("tags", "")
    cleaned_tags = _clean_and_deduplicate_tags(raw_tags)
    
    # Validate and clean genre
    raw_genre = combined_data.get("genre")
    validated_genre = get_validated_genre(raw_genre, fallback_to_uncategorized=True)
    
    return {
        "genre": validated_genre,
        "tags": cleaned_tags,
        "titles": [clean_title] + combined_data.get("title_variations", []),
        "description_short": combined_data.get("description_short"),
        "description_long": combined_data.get("description_long"),
        "language": known_lang_name
    }

# --- LOGIK FÜR MUSIKVIDEOS & AUDIO ---

def _normalize_date_string(date_value):
    if not date_value:
        return ""
    date_value = str(date_value).strip()
    if re.match(r"^\d{8}$", date_value):
        return f"{date_value[:4]}-{date_value[4:6]}-{date_value[6:8]}"
    if re.match(r"^\d{4}-\d{2}-\d{2}$", date_value):
        return date_value
    if re.match(r"^\d{4}$", date_value):
        return date_value
    match = re.search(r"\d{4}-\d{2}-\d{2}", date_value)
    if match:
        return match.group(0)
    match = re.search(r"\d{4}", date_value)
    if match:
        return match.group(0)
    return ""

def _normalize_release_date(release_date, upload_date):
    normalized_upload_date = _normalize_date_string(upload_date)
    normalized_release_date = _normalize_date_string(release_date)
    return normalized_release_date or normalized_upload_date or "2000-01-01"

def _coerce_bool(value):
    if isinstance(value, bool):
        return value
    if isinstance(value, (int, float)):
        return value != 0
    if isinstance(value, str):
        return value.strip().lower() in {"true", "yes", "y", "1"}
    return False

def _build_music_metadata_prompt(video_title, channel_name, upload_date, description, detect_music):
    """
    Builds the music metadata prompt components (system and user messages).
    Returns a tuple: (system_message, user_prompt)
    """
    description_text = (description or "").strip()
    description_snippet = description_text[:1500]
    detect_music_notice = ""
    detect_music_rule = ""
    music_field = ""
    if detect_music:
        detect_music_notice = "Bestimme zusätzlich, ob es sich um Musik handelt."
        detect_music_rule = """
    7. Wenn es kein Musikstück ist (Interview, Podcast, Ambiente, Geräusch-Loop, Dialog), setze "is_music": false und nutze Kanalname + Originaltitel.
    8. Wenn es Musik ist, setze "is_music": true und nutze die Regeln 1-6.
    """
        music_field = ',\n        "is_music": true'

    # System message (static, will be cached)
    system_message = f"""Du bist ein Musikarchivar und erkennst offizielle sowie inoffizielle Musikuploads.

REGELN:
1. Korrigiere Tippfehler (z.B. 'Billie Jeen' -> 'Billie Jean').
2. Löse Abkürzungen auf (z.B. 'MJ' -> 'Michael Jackson', 'AC DC' -> 'AC/DC').
3. Entferne Rauschen: '(Official Video)', '[4K]', 'Lyrics', 'Remastered', 'HD'.
4. Bewahre Sonderversionen im Titel (Remix, Live, Acoustic, Extended, Edit, Version, Cover).
5. Features: Hauptkünstler in "artist", Feature in Titel als 'Song (feat. X)'.
6. Wenn Kanalname = Künstler (z.B. 'QueenVEVO'), nimm den sauberen Namen ('Queen').
{detect_music_rule}
9. is_unofficial: true bei Fan-Uploads, inoffiziellen Remixes/Mashups, Nightcore, AMVs oder Dialog-Clips.
10. release_year: Bestimme das offizielle Veröffentlichungsjahr des Songs (meist Album-Release in Europa).
    - Bei offiziellen Songs: Nutze das Jahr der Erstveröffentlichung des Songs/Albums.
    - Bei speziellen Remixes oder Neuauflagen (neu eingesungen, nicht nur Remastered): Nutze das Jahr dieser Version.
    - Bei inoffiziellen Fan-Releases ohne Eintrag in Musikdatenbanken: setze null (dann wird Upload-Jahr verwendet).
    - Format: YYYY (nur Jahr) oder null.
11. album: Bestimme den Album-Namen, falls verfügbar.
    - Bei offiziellen Songs: Nutze den Namen des Albums, auf dem der Song erschienen ist.
    - Bei Singles ohne Album: setze null.
    - Bei Musikvideos, die nicht zu einem Album gehören: setze null.
    - Format: String oder null.
12. track_number: Bestimme die Tracknummer auf dem Album, falls verfügbar.
    - Bei Songs auf einem Album: Nutze die Position des Songs auf dem Album (z.B. 1, 2, 3, ...).
    - Wenn die Tracknummer unbekannt ist: setze null.
    - Format: Integer (1, 2, 3, ...) oder null.

FORMAT (JSON):
{{
    "artist": "Künstler",
    "title": "Songtitel",
    "release_year": "YYYY oder null",
    "album": "Album-Name oder null",
    "track_number": Integer oder null,
    "is_unofficial": false{music_field}
}}"""

    # User message (video-specific, dynamic)
    user_prompt = f"""Video Title: "{video_title}"
Channel Name: "{channel_name}"
Upload Date: "{upload_date}"
Description: "{description_snippet}"
{detect_music_notice}"""

    return system_message, user_prompt

def _analyze_music_metadata(video_title, channel_name, upload_date, description=None, detect_music=False):
    api_key = CONFIG.get("claude_key")
    normalized_upload_date = _normalize_date_string(upload_date)
    
    # Extract year from upload date as fallback
    upload_year = None
    if normalized_upload_date:
        year_match = re.search(r'\d{4}', normalized_upload_date)
        if year_match:
            upload_year = year_match.group(0)
    
    fallback_release_date = normalized_upload_date or "2000-01-01"
    fallback_release_year = upload_year or "2000"
    
    default_result = {
        "artist": channel_name,
        "title": video_title,
        "release_date": fallback_release_date,
        "release_year": fallback_release_year,
        "is_unofficial": False,
        "is_music": True,
        "album": None,
        "track_number": None,
        "ai_success": False
    }
    if not api_key:
        print("   ⚠️ Kein Claude API-Key konfiguriert. Nutze Fallback-Werte.")
        return default_result

    client = anthropic.Anthropic(api_key=api_key)
    model_name = get_model_name(light=True)
    system_message, user_prompt = _build_music_metadata_prompt(video_title, channel_name, normalized_upload_date, description, detect_music)
    response = _get_json_response(client, model_name, user_prompt, max_tokens=512, system=system_message)
    if not response:
        print("   ⚠️ Claude konnte keine Antwort liefern. Nutze Fallback-Werte.")
        if detect_music:
            default_result["is_music"] = False
        return default_result

    is_music = _coerce_bool(response.get("is_music", True)) if detect_music else True

    is_unofficial = _coerce_bool(response.get("is_unofficial"))
    release_year = response.get("release_year")
    album = response.get("album")
    track_number = response.get("track_number")

    # Log what Claude found
    artist_found = response.get("artist")
    title_found = response.get("title")
    
    result = {
        "artist": artist_found or channel_name,
        "title": title_found or video_title,
        "is_unofficial": is_unofficial,
        "is_music": is_music,
        "ai_success": True,
        "album": album,
        "track_number": track_number
    }
    
    # Console logging for traceability
    if artist_found and artist_found != channel_name:
        print(f"   ✓ Claude hat Künstler bereinigt: '{channel_name}' → '{artist_found}'")
    else:
        print(f"   ✓ Claude hat Künstler erkannt: '{artist_found or channel_name}'")
    
    if title_found and title_found != video_title:
        print(f"   ✓ Claude hat Titel bereinigt: '{video_title[:50]}...' → '{title_found}'")
    else:
        print(f"   ✓ Claude hat Titel erkannt: '{title_found or video_title}'")
    
    # Log album and track number if found
    if album:
        print(f"   ✓ Claude hat Album ermittelt: '{album}'")
    
    if track_number:
        print(f"   ✓ Claude hat Tracknummer ermittelt: {track_number}")

    # Handle release year
    if not is_music:
        result.update({
            "artist": channel_name,
            "title": video_title,
            "is_unofficial": False,
            "album": None,
            "track_number": None
        })
        final_release_date = fallback_release_date
        final_release_year = fallback_release_year
        print(f"   ℹ️ Kein Musikstück → Nutze Upload-Jahr: {final_release_year}")
    elif is_unofficial:
        final_release_date = fallback_release_date
        final_release_year = fallback_release_year
        print(f"   ℹ️ Inoffizielles Release → Nutze Upload-Jahr: {final_release_year}")
    else:
        # Official music: use Claude's release year or fallback to upload year
        if release_year and re.match(r'^\d{4}$', str(release_year)):
            year_int = int(release_year)
            # Validate year is reasonable (1900-2100)
            if 1900 <= year_int <= 2100:
                final_release_year = str(release_year)
                final_release_date = f"{final_release_year}-01-01"
                print(f"   ✓ Claude hat Release-Jahr ermittelt: {final_release_year}")
            else:
                final_release_year = fallback_release_year
                final_release_date = fallback_release_date
                print(f"   ⚠️ Claude lieferte ungültiges Jahr ({release_year}) → Nutze Upload-Jahr: {final_release_year}")
        else:
            final_release_year = fallback_release_year
            final_release_date = fallback_release_date
            print(f"   ⚠️ Claude konnte kein Release-Jahr ermitteln → Nutze Upload-Jahr: {final_release_year}")

    result["release_date"] = final_release_date
    result["release_year"] = final_release_year
    return result

def analyze_music_video(video_title, channel_name, upload_date=None, description=None):
    """
    Spezialisiert auf Musikvideos: Trennt Artist & Title, korrigiert Typos.
    """
    return _analyze_music_metadata(video_title, channel_name, upload_date, description=description, detect_music=False)

def analyze_audio_metadata(video_title, channel_name, upload_date=None, description=None):
    """
    Ermittelt, ob Audio Musik ist und liefert passende Metadaten.
    """
    return _analyze_music_metadata(video_title, channel_name, upload_date, description=description, detect_music=True)
