import os
import json
import ctypes
import threading
import subprocess
from datetime import datetime
from tkinter import filedialog, messagebox, simpledialog
from tkinter import *
from tkinter.ttk import Progressbar
from concurrent.futures import ThreadPoolExecutor, as_completed

try:
    from mutagen.easyid3 import EasyID3
    from mutagen.id3 import ID3, APIC
except ImportError:
    messagebox.showerror("Fehler", "Die Bibliothek 'mutagen' ist nicht installiert. Bitte installieren Sie sie mit 'pip install mutagen'.")
    exit()

SETTINGS_FILE = "settings.json"
HISTORY_FILE = "history.json"

WEEKDAY_SHORT_DE = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]

DEFAULT_EXPORT_PATHS = {
    "Gottesdienst": "gottesdienste",
    "Hochzeit": "hochzeiten",
    "Weissagung": "weissagungen"
}

def load_settings():
    if os.path.exists(SETTINGS_FILE):
        with open(SETTINGS_FILE, "r") as f:
            return json.load(f)
    return {
        "export_paths": DEFAULT_EXPORT_PATHS.copy(),
        "threads": 0  # 0 = auto, use all available cores
    }

def save_settings(settings):
    with open(SETTINGS_FILE, "w") as f:
        json.dump(settings, f, indent=2)

def save_export_history(entry):
    history = []
    if os.path.exists(HISTORY_FILE):
        with open(HISTORY_FILE, "r") as f:
            history = json.load(f)
    history.append(entry)
    with open(HISTORY_FILE, "w") as f:
        json.dump(history, f, indent=2)

# Fix pixelation on high-DPI displays
try:
    ctypes.windll.shcore.SetProcessDpiAwareness(2)  # Per-monitor DPI awareness
except:
    try:
        ctypes.windll.user32.SetProcessDPIAware()  # Fallback for older Windows
    except:
        pass

settings = load_settings()

root = Tk()
root.iconbitmap("icon.ico")
root.title("fcco Converter")
root.geometry("800x1200")

Label(root, text="Wähle WAV-Dateien aus:").pack(pady=5)
file_listbox = Listbox(root, width=60)
file_listbox.pack(pady=5)

file_status_listbox = Listbox(root, width=60)
file_status_listbox.pack(pady=5)

progress_bars = []  # List to store per-file progress bars

file_paths = []
is_converting = False  # Track conversion status
conversion_running = False  # Prevent multiple conversions

def choose_files():
    global file_paths
    file_paths = filedialog.askopenfilenames(filetypes=[("WAV-Dateien", "*.wav")])
    file_listbox.delete(0, END)
    file_status_listbox.delete(0, END)
    for bar in progress_bars:
        bar.destroy()
    progress_bars.clear()
    for path in file_paths:
        file_listbox.insert(END, os.path.basename(path))
        file_status_listbox.insert(END, f"0% – {os.path.basename(path)} – Wartet...")
        progress = Progressbar(root, orient=HORIZONTAL, length=400, mode='determinate')
        progress.pack(pady=2)
        progress_bars.append(progress)

Button(root, text="Dateien auswählen", command=choose_files).pack(pady=5)

def generate_album_name(veranstaltung=""):
    return datetime.now().strftime("%d.%m.%Y") + (f" {veranstaltung}" if veranstaltung else "")

def generate_folder_name(date_part, veranstaltung):
    return f"{date_part} {veranstaltung}" if veranstaltung else date_part

def extract_event_from_folder(file_path):
    folder_name = os.path.basename(os.path.dirname(file_path))
    if len(folder_name) >= 8 and folder_name[:6].isdigit() and folder_name[6:8] in WEEKDAY_SHORT_DE:
        return folder_name[:8], folder_name[8:].strip() if len(folder_name) > 8 else ""
    return "", ""

def open_settings_window():
    win = Toplevel(root)
    win.iconbitmap("icon.ico")
    win.title("Einstellungen")
    win.geometry("500x550")

    Label(win, text="Künstlername:").pack()
    artist_entry = Entry(win, width=40)
    artist_entry.pack()
    artist_entry.insert(0, settings.get("metadata", {}).get("artist", ""))

    Label(win, text="Cover Pfad:").pack()
    cover_entry = Entry(win, width=40)
    cover_entry.pack()
    cover_entry.insert(0, settings.get("metadata", {}).get("cover", ""))

    Label(win, text="Anzahl Threads (0 = Auto):").pack(pady=10)
    threads_entry = Entry(win, width=40)
    threads_entry.pack()
    threads_entry.insert(0, str(settings.get("threads", 0)))

    Label(win, text="Standardverzeichnisse für Exporte:").pack(pady=10)

    entries = {}
    for key in DEFAULT_EXPORT_PATHS:
        Label(win, text=key).pack()
        entry = Entry(win, width=40)
        entry.pack()
        entry.insert(0, settings.get("export_paths", {}).get(key, DEFAULT_EXPORT_PATHS[key]))
        entries[key] = entry

    def save_all():
        try:
            threads = int(threads_entry.get())
            if threads < 0:
                messagebox.showerror("Fehler", "Threads müssen 0 oder größer sein.")
                return
        except ValueError:
            messagebox.showerror("Fehler", "Bitte geben Sie eine gültige Zahl für Threads ein.")
            return

        settings["metadata"] = {
            "artist": artist_entry.get(),
            "cover": cover_entry.get()
        }
        settings["export_paths"] = {k: v.get() for k, v in entries.items()}
        settings["threads"] = threads
        save_settings(settings)
        messagebox.showinfo("Gespeichert", "Einstellungen wurden gespeichert.")
        win.destroy()

    Button(win, text="Speichern", command=save_all).pack(pady=10)

Button(root, text="Einstellungen öffnen", command=open_settings_window).pack(pady=5)

def confirm_metadata_and_type():
    meta = settings.get("metadata", {}).copy()
    if file_paths:
        date_part, veranstaltung = extract_event_from_folder(file_paths[0])
        if not date_part:
            date_part = datetime.now().strftime("%y%m%d") + WEEKDAY_SHORT_DE[datetime.now().weekday()]
    else:
        date_part = datetime.now().strftime("%y%m%d") + WEEKDAY_SHORT_DE[datetime.now().weekday()]
        veranstaltung = ""
    meta["album"] = generate_album_name(veranstaltung)
    meta["year"] = str(datetime.now().year)

    win = Toplevel(root)
    win.iconbitmap("icon.ico")
    win.title("Metadaten und Exportoptionen")
    win.geometry("650x600")

    Label(win, text="Künstlername:").pack()
    artist_entry = Entry(win, width=40)
    artist_entry.pack()
    artist_entry.insert(0, meta.get("artist", ""))

    Label(win, text="Albumname:").pack()
    album_entry = Entry(win, width=40)
    album_entry.pack()
    album_entry.insert(0, meta.get("album", ""))

    Label(win, text="Jahr:").pack()
    year_entry = Entry(win, width=40)
    year_entry.pack()
    year_entry.insert(0, meta.get("year", ""))

    Label(win, text="Cover-Pfad:").pack()
    cover_entry = Entry(win, width=40)
    cover_entry.pack()
    cover_entry.insert(0, meta.get("cover", ""))

    Label(win, text="Art des Inhalts:").pack(pady=10)
    content_type = StringVar(value="Gottesdienst")
    for option in ["Gottesdienst", "Hochzeit", "Weissagung", "Sonstiges"]:
        Radiobutton(win, text=option, variable=content_type, value=option).pack(anchor=W)

    date_event_frame = Frame(win)
    date_event_frame.pack(pady=10)

    Label(date_event_frame, text="Datum:").grid(row=0, column=0, sticky="ew")
    date_entry = Entry(date_event_frame, width=20)
    date_entry.grid(row=1, column=0, padx=5, pady=5)
    date_entry.insert(0, date_part)

    Label(date_event_frame, text="Veranstaltung (optional):").grid(row=0, column=1, sticky="ew")
    event_entry = Entry(date_event_frame, width=20)
    event_entry.grid(row=1, column=1, padx=5, pady=5)
    event_entry.insert(0, veranstaltung)

    def update_album_name(*args):
        event = event_entry.get().strip()
        album_entry.delete(0, END)
        album_entry.insert(0, generate_album_name(event))

    event_entry.bind("<KeyRelease>", update_album_name)

    confirmed_data = {}

    def confirm():
        confirmed_data["artist"] = artist_entry.get()
        confirmed_data["album"] = album_entry.get()
        confirmed_data["year"] = year_entry.get()
        confirmed_data["cover"] = cover_entry.get()
        confirmed_data["type"] = content_type.get()
        confirmed_data["event"] = event_entry.get().strip()
        confirmed_data["date_part"] = date_entry.get().strip()
        win.destroy()

    Button(win, text="Bestätigen", command=confirm).pack(pady=10)
    win.grab_set()
    root.wait_window(win)

    return confirmed_data if confirmed_data else None

def convert_file(file, index, export_dir, meta):
    try:
        title = os.path.splitext(os.path.basename(file))[0]
        mp3_path = os.path.join(export_dir, f"{title}.mp3")
        threads = settings.get("threads", 0)
        cmd = [
            "ffmpeg",
            "-i", file,
            "-threads", str(threads) if threads > 0 else "0",
            "-c:a", "mp3",
            "-b:a", "192k",
            "-y",
            mp3_path
        ]
        # Simulate progress for UI
        for i in range(1, 101):
            root.after(100, lambda i=i, idx=index: update_progress(idx, i, title, "Konvertiert..."))
        # Redirect stdout and stderr to DEVNULL to avoid decoding issues
        subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

        audiofile = EasyID3(mp3_path)
        audiofile["title"] = title
        audiofile["artist"] = meta["artist"] or "Unbekannt"
        audiofile["album"] = meta["album"] or "Unbekanntes Album"
        audiofile["tracknumber"] = str(index + 1)
        audiofile["date"] = meta.get("year", "")
        audiofile.save()

        audiofile = ID3(mp3_path)
        with open(meta["cover"], "rb") as img:
            audiofile.add(APIC(
                encoding=3,
                mime='image/jpeg',
                type=3,
                desc=u'Cover',
                data=img.read()
            ))
        audiofile.save()

        return (index, f"{title}.mp3", None)
    except subprocess.CalledProcessError as e:
        return (index, os.path.basename(file), f"FFmpeg Error: {str(e)}")
    except Exception as e:
        return (index, os.path.basename(file), str(e))

def update_progress(index, percentage, filename, status):
    file_status_listbox.delete(index)
    file_status_listbox.insert(index, f"{percentage}% – {filename} – {status}")
    progress_bars[index]["value"] = percentage
    root.update_idletasks()

def convert():
    global is_converting, conversion_running
    if not file_paths or conversion_running:
        if not file_paths:
            messagebox.showerror("Fehler", "Keine Dateien ausgewählt.")
        return

    conversion_running = True
    result = confirm_metadata_and_type()
    if not result:
        conversion_running = False
        return

    meta = result
    content_type = meta["type"]
    veranstaltung = meta.get("event", "").strip()
    date_part = meta.get("date_part", datetime.now().strftime("%y%m%d") + WEEKDAY_SHORT_DE[datetime.now().weekday()])

    if content_type in settings.get("export_paths", {}) and content_type != "Sonstiges":
        base_dir = settings["export_paths"][content_type]
        os.makedirs(base_dir, exist_ok=True)
        folder_name = generate_folder_name(date_part, veranstaltung)
        export_dir = os.path.join(base_dir, folder_name)
        os.makedirs(export_dir, exist_ok=True)
    else:
        export_dir = filedialog.askdirectory(title="Export-Ordner wählen")
        if not export_dir:
            conversion_running = False
            return

    settings["last_export_dir"] = export_dir
    save_settings(settings)

    sorted_files = sorted(file_paths, key=lambda x: os.path.basename(x).lower())
    total = len(sorted_files)
    file_status_listbox.delete(0, END)
    for f in sorted_files:
        file_status_listbox.insert(END, f"0% – {os.path.basename(f)} – Wartet...")

    def run_conversion():
        global is_converting, conversion_running
        is_converting = True
        successful_files = []
        max_workers = settings.get("threads", 0) or os.cpu_count() or 1

        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            future_to_file = {executor.submit(convert_file, file, idx, export_dir, meta): idx for idx, file in enumerate(sorted_files)}
            for future in as_completed(future_to_file):
                index = future_to_file[future]
                result = future.result()
                index, filename, error = result
                if error:
                    update_progress(index, 100, filename, f"Fehler: {error}")
                else:
                    update_progress(index, 100, filename, "Fertig!")
                    successful_files.append(filename)

        save_export_history({
            "datum": datetime.now().strftime("%d.%m.%Y %H:%M"),
            "ordner": export_dir,
            "dateien": successful_files
        })

        messagebox.showinfo("Fertig", "Konvertierung abgeschlossen!")
        is_converting = False
        conversion_running = False

    threading.Thread(target=run_conversion).start()

def on_closing():
    global is_converting
    if is_converting:
        if messagebox.askyesno("Bestätigung", "Konvertierung läuft. Möchten Sie das Programm wirklich beenden?"):
            root.destroy()
    else:
        root.destroy()

Button(root, text="Konvertieren", command=convert).pack(pady=10)

root.protocol("WM_DELETE_WINDOW", on_closing)

root.mainloop()