import os
import json
import re
import subprocess
from mutagen.mp4 import MP4, MP4Cover
# RELATIVE IMPORTS
from .config import CONFIG
from .utils import run_shell, get_state, save_state
from .module_subtitles import extract_subtitles_from_video

def _get_total_bitrate(video_path, audio_path=None):
    """
    Get total bitrate (video + audio) from encoded files.
    Returns bitrate in Mbit/s or None if it cannot be determined.
    """
    try:
        total_bps = 0
        
        for path in [video_path, audio_path]:
            if path is None or not os.path.exists(path):
                continue
            cmd = [
                CONFIG['ffprobe'],
                '-v', 'error',
                '-show_entries', 'format=bit_rate',
                '-of', 'json',
                path
            ]
            result = subprocess.run(cmd, capture_output=True, text=True)
            data = json.loads(result.stdout)
            
            if 'format' in data:
                bitrate_str = data['format'].get('bit_rate')
                if bitrate_str and bitrate_str != 'N/A':
                    total_bps += int(bitrate_str)
        
        if total_bps > 0:
            return total_bps / 1_000_000  # Convert to Mbit/s
        return None
    except Exception:
        return None

def get_source_video(job_dir):
    for f in os.listdir(job_dir):
        if f.startswith("original.") and f.endswith((".mkv", ".mp4", ".webm")):
            return os.path.join(job_dir, f)
    raise FileNotFoundError(f"Kein Original-Video in {job_dir} gefunden!")

def time_to_ms(time_str):
    try:
        parts = time_str.strip().split(':')
        if len(parts) == 3:
            seconds = int(parts[0]) * 3600 + int(parts[1]) * 60 + float(parts[2])
        elif len(parts) == 2:
            seconds = int(parts[0]) * 60 + float(parts[1])
        else: return 0
        return int(seconds * 1000)
    except: return 0

def format_iso_date(date_value, fallback_raw_date=None):
    if not date_value:
        date_value = fallback_raw_date
    if not date_value:
        return "2000-01-01T12:00:00Z"
    date_value = str(date_value).strip()
    if re.match(r"^\d{8}$", date_value):
        base_date = f"{date_value[:4]}-{date_value[4:6]}-{date_value[6:8]}"
    elif re.match(r"^\d{4}-\d{2}-\d{2}$", date_value):
        base_date = date_value
    elif re.match(r"^\d{4}$", date_value):
        base_date = f"{date_value}-01-01"
    else:
        match = re.search(r"\d{4}-\d{2}-\d{2}", date_value)
        if match:
            base_date = match.group(0)
        else:
            match = re.search(r"\d{4}", date_value)
            base_date = f"{match.group(0)}-01-01" if match else "2000-01-01"
    return f"{base_date}T12:00:00Z"

def create_ffmetadata_file(chapters_txt, output_meta, duration_sec):
    if not os.path.exists(chapters_txt): return False
    chapters = []
    try:
        with open(chapters_txt, 'r', encoding='utf-8') as f:
            for line in f:
                match = re.match(r"(\d{1,2}:\d{2}(?::\d{2})?)\s-\s(.*)", line.strip())
                if match:
                    ms = time_to_ms(match.group(1))
                    chapters.append({"start_ms": ms, "title": match.group(2)})
    except: return False

    if not chapters: return False
    content = ";FFMETADATA1\n"
    for i, chap in enumerate(chapters):
        start = chap["start_ms"]
        if i < len(chapters) - 1: end = chapters[i+1]["start_ms"]
        else: end = int(duration_sec * 1000)
        if end <= start: end = start + 1000
        content += f"[CHAPTER]\nTIMEBASE=1/1000\nSTART={start}\nEND={end}\ntitle={chap['title']}\n\n"
    try:
        with open(output_meta, 'w', encoding='utf-8') as f: f.write(content)
        return True
    except: return False

def find_best_poster(job_dir):
    candidates = ["poster.jpg", "original.jpg", "original.webp", "original.png"]
    for c in candidates:
        p = os.path.join(job_dir, c)
        if os.path.exists(p): return p
    return None

def ensure_jpg(input_path, output_path):
    if input_path.lower().endswith(".jpg") or input_path.lower().endswith(".jpeg"):
        run_shell(f"cp '{input_path}' '{output_path}'")
    else:
        run_shell(f"{CONFIG['ffmpeg']} -i '{input_path}' -q:v 2 -y '{output_path}' -hide_banner -loglevel error")

def sanitize_filename(name):
    name = str(name).replace("/", "-").replace("\\", "-").replace(":", " -")
    return re.sub(r'(?u)[^-\w. ()]', '', name).strip()

def get_next_track_number(target_name, album_name):
    """
    Ermittelt die nächste Tracknummer, indem alle Tracknummern aus den Dateien
    im Ziel-Sampler-Verzeichnis ausgelesen werden und das Maximum + 1 zurückgegeben wird.
    """
    try:
        rclone_root = CONFIG.get("rclone_root")
        if not rclone_root:
            return 1
        
        # Sanitize album_name for directory path
        clean_album = (album_name.replace("/", "_")
                                 .replace("\\", "_")
                                 .replace("'", "")
                                 .replace('"', "")
                                 .strip())
        
        remote_path = f"{rclone_root}/{target_name}/{clean_album}"
        
        # List all files in the remote directory
        cmd = ["rclone", "lsf", remote_path, "--files-only"]
        result = subprocess.run(cmd, capture_output=True, text=True, check=False)
        
        if result.returncode != 0:
            # Directory doesn't exist yet or error occurred
            return 1
        
        files = result.stdout.strip().splitlines()
        if not files:
            return 1
        
        # Extract all track numbers from filenames
        track_numbers = []
        for filename in files:
            match = re.match(r'^(\d{3}) - ', filename)
            if match:
                track_numbers.append(int(match.group(1)))
        
        # Return the maximum track number + 1, or 1 if no tracks found
        if track_numbers:
            return max(track_numbers) + 1
        
        return 1
    except Exception as e:
        print(f"   ⚠️  Konnte nächste Tracknummer nicht ermitteln: {e}")
        return 1

# --- DIST BUILD (M4V) ---
def build_dist(job_dir):
    state = get_state(job_dir)
    json_path = os.path.join(job_dir, "original.info.json")
    with open(json_path, "r") as f: meta = json.load(f)
    
    source_video = get_source_video(job_dir)
    extract_subtitles_from_video(job_dir, source_video)
    
    raw_date = meta.get("upload_date", "20000101")
    channel = meta.get("channel") or meta.get("uploader") or "Unknown"
    channel = channel.replace('"', "'")
    
    target_name = state.get("target", "Allgemein")
    
    # Safety check: "Auto" should have been resolved during curation
    if target_name == "Auto":
        print("   ⚠️  Target ist noch 'Auto' - sollte während der Kuratierung aufgelöst worden sein")
        print("   ℹ️  Fallback zum ersten verfügbaren Ziel")
        from .config import get_available_targets
        targets = get_available_targets()
        if targets:
            target_name = targets[0]
            print(f"   ℹ️  Verwende Fallback-Ziel: {target_name}")
            # Update state with fallback
            state["target"] = target_name
            save_state(job_dir, state)
        else:
            raise Exception("Keine verfügbaren Zielverzeichnisse in der Config!")
    
    is_music_video = (target_name == "Musikvideos" or target_name == "Musik")
    if is_music_video:
        # For music videos, use only the release year (not full date)
        release_year = state.get("curated_release_year")
        if release_year:
            iso_date = format_iso_date(release_year)
        else:
            # Fallback to upload date year
            upload_year = raw_date[:4] if raw_date and len(raw_date) >= 4 else "2000"
            iso_date = format_iso_date(upload_year)
    else:
        iso_date = format_iso_date(raw_date)

    # --- NAMENSGEBUNG ---
    if is_music_video:
        artist = state.get("curated_artist", channel)
        song_title = state.get("curated_title", meta.get("title"))
        album_name = state.get("curated_album", "Unsorted")
        
        # Automatisch die nächste Tracknummer ermitteln
        track = get_next_track_number(target_name, album_name)
        print(f"   🔢 Tracknummer automatisch ermittelt: {track:03d}")
        
        clean_artist = sanitize_filename(artist)
        clean_title = sanitize_filename(song_title)
        
        final_m4v_name = f"{int(track):03d} - {clean_artist} - {clean_title}"
        container_name = album_name
        genre = "Musikvideo"
        
        tag_title = song_title
        tag_artist = artist
        tag_album = ""  # Album no longer stored in metadata
        tag_album_artist = ""  # Also removed
        print(f"   🎵 Modus: Musikvideo")
    else:
        final_m4v_name = state.get("final_name") or f"video_{meta.get('id')}"
        container_name = channel
        genre = state.get("curated_genre", "Internet")
        
        tag_title = state.get("curated_title", meta.get("title"))
        tag_artist = channel
        tag_album = channel
        tag_album_artist = channel
        print(f"   🌍 Modus: Internetvideo")

    print(f"🎬 Ziel-Datei: {final_m4v_name}.m4v")

    # PHASE 2: Audio
    audio_tmp = os.path.join(job_dir, "audio_norm.m4a")
    if not os.path.exists(audio_tmp):
        print(f"\n🔊 --- PHASE 2: AUDIO NORMALISIERUNG ---")
        
        # Build audio filter chain
        audio_filters = "aresample=async=1,loudnorm=I=-16:TP=-1.5:LRA=11"
        
        # Add fade-out for internet videos only (not music videos)
        if not is_music_video and CONFIG.get("audio_fadeout_enabled", True):
            duration = meta.get("duration", 0)
            # Ensure duration is a valid number and long enough for a meaningful fade-out
            if isinstance(duration, (int, float)) and duration > 2.0:
                fade_start = duration - 1.5
                audio_filters += f",afade=t=out:st={fade_start}:d=1.5"
                print(f"   🔉 Audio-Fadeout aktiviert (1.5s ab {fade_start:.1f}s)")
        
        audio_cmd = (f"{CONFIG['ffmpeg']} -i '{source_video}' -vn -c:a aac_at -b:a 256k -ar 48000 -ac 2 "
                     f"-af '{audio_filters}' -y '{audio_tmp}' "
                     f"-hide_banner -loglevel error -stats")
        print(f"   🔧 {audio_cmd}")
        run_shell(audio_cmd)
    
    # PHASE 3: Video (Adaptives Encoding)
    video_tmp = os.path.join(job_dir, "video_enc.mp4")
    if not os.path.exists(video_tmp):
        quality = CONFIG.get("video_quality", 70)
        threshold_mbps = CONFIG.get("bitrate_threshold_mbps", 100)
        quality_step = CONFIG.get("adaptive_quality_step", 2)
        print(f"\n🎞️ --- PHASE 3: VIDEO ENCODING (Adaptiv) ---")
        print(f"   ⚙️  Ziel-Qualität: {quality}")
        print(f"   ⚙️  Bitrate-Limite: {threshold_mbps} Mbit/s (Video+Audio)")
        
        attempt = 0
        max_attempts = 10
        while True:
            attempt += 1
            # VideoToolbox: higher value = higher quality = larger file
            # To reduce bitrate, we DECREASE quality
            current_quality = max(quality - (attempt - 1) * quality_step, 10)
            
            if attempt > max_attempts:
                print(f"   ⚠️  Maximale Versuche ({max_attempts}) erreicht – verwende letztes Ergebnis")
                break
            
            if attempt > 1:
                # Remove previous encode for re-encoding
                if os.path.exists(video_tmp):
                    os.remove(video_tmp)
                print(f"\n   🔄 Erneutes Encoding (Versuch {attempt}): Qualität {current_quality} (-{quality_step * (attempt - 1)})")
            else:
                print(f"   🎬 Encoding mit Qualität {current_quality}...")
            
            video_cmd = (f"{CONFIG['ffmpeg']} -i '{source_video}' "
                        f"-c:v hevc_videotoolbox -profile:v main10 -pix_fmt p010le "
                        f"-q:v {current_quality} -g 30 "
                        f"-fps_mode cfr -an "
                        f"-movflags +faststart "
                        f"-y '{video_tmp}' "
                        f"-hide_banner -loglevel error -stats")
            print(f"   🔧 {video_cmd}")
            run_shell(video_cmd)
            
            # Bitratenprüfung (Video + Audio)
            total_bitrate = _get_total_bitrate(video_tmp, audio_tmp)
            if total_bitrate is not None:
                print(f"   📊 Gesamtbitrate (Video+Audio): {total_bitrate:.1f} Mbit/s (Limite: {threshold_mbps} Mbit/s)")
                if total_bitrate <= threshold_mbps:
                    print(f"   ✅ Bitrate OK – unter {threshold_mbps} Mbit/s")
                    break
                else:
                    print(f"   ⚠️  Bitrate {total_bitrate:.1f} Mbit/s überschreitet Limite von {threshold_mbps} Mbit/s")
            else:
                print(f"   ⚠️  Bitrate konnte nicht ermittelt werden – überspringe adaptive Anpassung")
                break
    else:
        # File already exists, just report bitrate
        total_bitrate = _get_total_bitrate(video_tmp, audio_tmp)
        if total_bitrate is not None:
            print(f"   📊 Gesamtbitrate (Video+Audio): {total_bitrate:.1f} Mbit/s")
    
    # PHASE 4: Muxing
    final_m4v_path = os.path.join(job_dir, f"{final_m4v_name}.m4v")
    print(f"\n📦 --- PHASE 4: MUXING ---")
    
    # 1. UNTERTITEL SUCHE (Mehrsprachig)
    lang_code = state.get("language_code", "und") # Originalsprache
    lang_2 = lang_code[:2] if lang_code else "en"
    
    selected_subs = [] # Liste von Tupeln: (path, lang_code_iso, title)

    vtts = [f for f in os.listdir(job_dir) if f.endswith(".vtt")]
    
    # Helper zum Finden einer VTT für eine bestimmte Sprache
    def find_vtt_for_lang(target_lang_2, target_lang_3):
        candidates = []
        search_terms = [f".{target_lang_2}.", f"-{target_lang_2}.", f"_{target_lang_2}.", f".{target_lang_2}-", f".{target_lang_3}."]
        if target_lang_2 == "de": search_terms.extend([".deu.", ".ger."])
        if target_lang_2 == "en": search_terms.extend([".eng."])
        
        for v in vtts:
            v_lower = v.lower()
            for term in search_terms:
                if term in v_lower:
                    candidates.append(v)
                    break
        # Sortieren: Ohne 'orig' bevorzugt, dann Kürze
        candidates.sort(key=lambda x: (1 if "orig" in x else 0, len(x)))
        return os.path.join(job_dir, candidates[0]) if candidates else None

    # A) Hauptsprache (Original)
    main_vtt = find_vtt_for_lang(lang_2, lang_code)
    # Fallback für Hauptsprache: Wenn nix gefunden, nimm irgendwas mit 'orig' oder erste Datei
    if not main_vtt:
        for v in vtts:
            if "orig" in v.lower(): main_vtt = os.path.join(job_dir, v); break
    if not main_vtt and vtts: main_vtt = os.path.join(job_dir, vtts[0])

    if main_vtt:
        # Bestimme Label für Hauptspur
        title = "Deutsch" if lang_2 == "de" else ("English" if lang_2 == "en" else lang_code)
        # Korrektur falls wir versehentlich falsche Datei als Hauptspur genommen haben (z.B. Video DE, aber nur ENG Sub da)
        if ".en." in os.path.basename(main_vtt).lower() or ".eng." in os.path.basename(main_vtt).lower():
             title = "English"
             lang_code = "eng"
        
        selected_subs.append({
            "path": main_vtt,
            "lang": lang_code,
            "title": title,
            "is_default": False # Keine Spur auf Default zwingen, Player entscheiden lassen
        })

    # B) Englisch (Zusätzlich), falls Original nicht Englisch ist
    if lang_2 != "en":
        eng_vtt = find_vtt_for_lang("en", "eng")
        # Füge hinzu, aber nur wenn es nicht exakt dieselbe Datei wie Hauptspur ist
        if eng_vtt and eng_vtt != main_vtt:
            selected_subs.append({
                "path": eng_vtt,
                "lang": "eng",
                "title": "English",
                "is_default": False
            })

    # 2. INPUTS BAUEN
    cmd_inputs = [f"-i '{video_tmp}'", f"-i '{audio_tmp}'", f"-i '{source_video}'"]
    input_map_str = "-map 0:v -c:v copy -map 1:a -c:a copy"
    current_idx = 3 
    
    # 3. KAPITEL
    chapters_txt = os.path.join(job_dir, "chapters.txt")
    metadata_file = os.path.join(job_dir, "ffmetadata.txt")
    map_chap_arg = "-map_chapters -1"

    if not is_music_video:
        if os.path.exists(chapters_txt):
            duration = meta.get("duration", 0)
            if create_ffmetadata_file(chapters_txt, metadata_file, duration):
                cmd_inputs.append(f"-i '{metadata_file}'")
                map_chap_arg = f"-map_metadata {current_idx} -map_chapters {current_idx}"
                current_idx += 1
            else:
                map_chap_arg = "-map_chapters 2" 

    # 4. UNTERTITEL EINBINDEN
    map_subs_arg = ""
    sub_output_index = 0
    
    # Inputs hinzufügen und Map-Commands bauen
    # Wir haben schon cmd_inputs (Video, Audio, Orig-Video, evtl Meta)
    # Merke: current_idx ist der nächste freie Input-Index
    
    for sub in selected_subs:
        print(f"   💬 Muxe Untertitel ({sub['title']}): {os.path.basename(sub['path'])}")
        cmd_inputs.append(f"-i '{sub['path']}'")
        
        map_subs_arg += (
            f" -map {current_idx}:s " # Nimm Stream aus diesem Input
            f"-c:s mov_text "
            f"-disposition:s:{sub_output_index} 0 " # Explizit für diesen Output-Stream auf 'none' setzen
            f"-metadata:s:s:{sub_output_index} language={sub['lang']} "
            f"-metadata:s:s:{sub_output_index} title=\"{sub['title']}\" "
            f"-metadata:s:s:{sub_output_index} handler_name=\"{sub['title']}\" "
        )
        current_idx += 1
        sub_output_index += 1
    
    full_input_str = " ".join(cmd_inputs)

    run_shell(f"{CONFIG['ffmpeg']} {full_input_str} "
              f"{input_map_str} "
              f"{map_subs_arg} "
              f"{map_chap_arg} "
              f"-metadata:s:a:0 language={lang_code} "
              f"-f mp4 -tag:v hvc1 -movflags +faststart -y '{final_m4v_path}' "
              f"-hide_banner -loglevel error -stats")
    
    # PHASE 5: Tagging (mutagen)
    print(f"\n🏷️ --- PHASE 5: TAGGING ---")

    # Musikvideos erhalten keine Beschreibungen (Issue: Remove description on music video)
    if is_music_video:
        short_desc = ""
        long_desc = ""
    else:
        short_desc = state.get("curated_description", meta.get("description", ""))[:250]
        long_desc = state.get("curated_long_description", meta.get("description", ""))

    mp4 = MP4(final_m4v_path)
    mp4["\xa9nam"] = [tag_title]
    mp4["\xa9ART"] = [tag_artist]
    mp4["\xa9day"] = [iso_date]
    mp4["\xa9gen"] = [genre]
    mp4["\xa9too"] = ["Patrick Kurmann"]
    mp4["desc"]    = [short_desc]
    mp4["ldes"]    = [long_desc]
    mp4["\xa9cmt"] = [f"{meta.get('extractor')}_{meta.get('id')}"]

    print(f"   🏷️ Titel: {tag_title}")
    print(f"   🏷️ Künstler: {tag_artist}")

    if not is_music_video:
        mp4["aART"]    = [tag_album_artist]
        mp4["\xa9alb"] = [tag_album]
        print(f"   🏷️ Album: {tag_album}")

    # Tags als Grouping für Apple/Infuse
    tags = state.get("curated_tags", "")
    if tags:
        clean_tags = tags.replace(", ", " / ").replace(",", "/")
        mp4["\xa9grp"] = [clean_tags]
        print(f"   🏷️ Grouping: {clean_tags}")

    width = meta.get("width", 0)
    hd_flag = 3 if width >= 3800 else (2 if width >= 1900 else (1 if width >= 1200 else 0))
    mp4["hdvd"] = [hd_flag]

    if is_music_video:
        mp4["stik"] = [6]
        mp4["cpil"] = True
        print(f"   🏷️ Typ: Musikvideo (Compilation)")
    else:
        mp4["stik"] = [9]
        print(f"   🏷️ Typ: Film")

    best_poster = find_best_poster(job_dir)
    if best_poster:
        is_jpeg = best_poster.lower().endswith((".jpg", ".jpeg"))
        fmt = MP4Cover.FORMAT_JPEG if is_jpeg else MP4Cover.FORMAT_PNG
        with open(best_poster, "rb") as f:
            mp4["covr"] = [MP4Cover(f.read(), imageformat=fmt)]
        print(f"   🏷️ Artwork: {os.path.basename(best_poster)}")

    mp4.save()
    print(f"   ✅ Metadaten geschrieben (mutagen)")
    
    # WICHTIG: final_name im State speichern, damit Deploy das File findet!
    # (Besonders wichtig für Musikvideos, wo der Name dynamisch in dieser Funktion entsteht)
    save_state(job_dir, {
        "status": "processed", 
        "m4v_ready": True, 
        "container_name": container_name,
        "final_name": final_m4v_name 
    })
    return final_m4v_path
