import os
import json
import urllib.request
import subprocess
import uuid
from pathlib import Path
import webbrowser
import requests
import http.server
import socketserver
from urllib.parse import urlparse, parse_qs
import threading
import shutil
import zipfile
import tarfile
import platform
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter.scrolledtext import ScrolledText
from datetime import datetime
from PIL import Image, ImageTk
import io
import tempfile
import logging
from logging.handlers import RotatingFileHandler

log_file = Path.home() / ".glitchclient" / "logs" / "glitchclient.log"
log_file.parent.mkdir(parents=True, exist_ok=True)
logger = logging.getLogger("GlitchClient")
logger.setLevel(logging.DEBUG)
fh = RotatingFileHandler(log_file, maxBytes=5*1024*1024, backupCount=3)
fh.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(ch)
logger.info("Initializing GlitchClient")
minecraft_dir = Path.home() / ".glitchclient"
versions_dir = minecraft_dir / "versions"
libraries_dir = minecraft_dir / "libraries"
assets_dir = minecraft_dir / "assets"
cache_dir = minecraft_dir / "cache"
mods_dir = minecraft_dir / "mods"
modpacks_dir = minecraft_dir / "modpacks"
first_run_file = cache_dir / "first_run"
token_file = cache_dir / "token.json"

logger.debug(f"Using minecraft directory: {minecraft_dir}")

if platform.system() == "Windows":
    logger.debug("Detected Windows platform")
    java_path = str(minecraft_dir / "java" / "bin" / "java.exe")
    minecraft_launcher_dir = Path(os.getenv("APPDATA")) / ".minecraft"
    client_url = "https://download.glitchclient.theerrorexe.dev/GlitchClient.exe"
elif platform.system() == "Darwin":
    logger.debug("Detected macOS platform")
    java_path = str(minecraft_dir / "java" / "bin" / "java")
    minecraft_launcher_dir = Path.home() / "Library" / "Application Support" / "minecraft"
    client_url = "https://download.glitchclient.theerrorexe.dev/GlitchClient.py"
else:
    logger.debug("Detected Linux platform")
    java_path = str(minecraft_dir / "java" / "bin" / "java")
    minecraft_launcher_dir = Path.home() / ".minecraft"
    client_url = "https://download.glitchclient.theerrorexe.dev/GlitchClient"

logger.error(f"Java path: {java_path}")
logger.debug(f"Minecraft launcher directory: {minecraft_launcher_dir}")

CLIENT_ID = "27843883-6e3b-42cb-9e51-4f55a700601e"
REDIRECT_URI = "http://localhost:8086"
AUTHORITY = "https://login.live.com/oauth20_authorize.srf"
TOKEN_URL = "https://login.live.com/oauth20_token.srf"
XBOX_LIVE_AUTH_URL = "https://user.auth.xboxlive.com/user/authenticate"
XSTS_AUTH_URL = "https://xsts.auth.xboxlive.com/xsts/authorize"
MINECRAFT_AUTH_URL = "https://api.minecraftservices.com/authentication/login_with_xbox"
MINECRAFT_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile"
MINECRAFT_ENTITLEMENTS_URL = "https://api.minecraftservices.com/entitlements/mcstore"
SCOPE = "XboxLive.signin offline_access"
PROMPT = "login"

for directory in [versions_dir, libraries_dir, assets_dir, cache_dir, mods_dir, modpacks_dir]:
    try:
        directory.mkdir(parents=True, exist_ok=True)
        logger.debug(f"Created directory: {directory}")
    except Exception as e:
        logger.error(f"Failed to create directory {directory}: {str(e)}")
        raise

class GUILogHandler(logging.Handler):
    def __init__(self, gui):
        super().__init__()
        self.gui = gui
        self.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
        
    def emit(self, record):
        msg = self.format(record)
        self.gui.log(msg)

class GlitchClientGUI:
    def __init__(self, root):
        logger.info("Initializing GlitchClient GUI")
        self.root = root
        self.root.title("GlitchClient")
        self.root.geometry("900x650")
        self.root.resizable(True, True)
        self.root.configure(bg="#1e1e2e")
        self.current_version = "1.0.2"
        self.temp_mods_dir = None
        self.style = ttk.Style()
        self.style.theme_use("clam")
        self.style.configure("TButton", padding=10, font=("Segoe UI", 12), background="#5e81ac", foreground="#ffffff", borderwidth=0)
        self.style.map("TButton", background=[("active", "#81a1c1")])
        self.style.configure("TLabel", font=("Segoe UI", 12), background="#1e1e2e", foreground="#d8dee9")
        self.style.configure("TEntry", font=("Segoe UI", 12), padding=5)
        self.style.configure("TCombobox", font=("Segoe UI", 12), padding=5)
        self.style.configure("TFrame", background="#1e1e2e")
        self.style.configure("TNotebook", background="#2e3440", foreground="#d8dee9")
        self.style.configure("TNotebook.Tab", font=("Segoe UI", 12), padding=[10, 5], background="#2e3440", foreground="#d8dee9")
        self.style.map("TNotebook.Tab", background=[("selected", "#5e81ac")], foreground=[("selected", "#ffffff")])
        self.container = ttk.Frame(self.root, padding="20")
        self.container.grid(row=0, column=0, sticky="nsew")
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        self.header_frame = ttk.Frame(self.container)
        self.header_frame.grid(row=0, column=0, sticky="ew", pady=(0, 20))
        self.title_label = ttk.Label(self.header_frame, text="GlitchClient", font=("Segoe UI", 28, "bold"), foreground="#88c0d0")
        self.title_label.pack(side="left")
        self.version_label = ttk.Label(self.header_frame, text=f"v{self.current_version}", font=("Segoe UI", 12))
        self.version_label.pack(side="left", padx=10)
        self.user_frame = ttk.Frame(self.header_frame)
        self.user_frame.pack(side="right")
        self.user_label = ttk.Label(self.user_frame, text="Nicht angemeldet", font=("Segoe UI", 12))
        self.user_label.pack(side="right", padx=5)
        self.head_label = ttk.Label(self.user_frame)
        self.head_label.pack(side="right")
        self.notebook = ttk.Notebook(self.container)
        self.notebook.grid(row=1, column=0, sticky="nsew")
        self.container.columnconfigure(0, weight=1)
        self.container.rowconfigure(1, weight=1)
        self.launcher_frame = ttk.Frame(self.notebook, padding="10")
        self.notebook.add(self.launcher_frame, text="Launcher")
        self.settings_frame = ttk.Frame(self.launcher_frame)
        self.settings_frame.grid(row=0, column=0, sticky="ew", pady=10)
        self.ram_label = ttk.Label(self.settings_frame, text="RAM:")
        self.ram_label.grid(row=0, column=0, sticky="e", padx=10, pady=5)
        self.ram_var = tk.StringVar(value="4G")
        self.ram_menu = ttk.Combobox(self.settings_frame, textvariable=self.ram_var, values=["2G", "4G", "6G", "8G", "12G", "16G"], state="readonly", width=10)
        self.ram_menu.grid(row=0, column=1, sticky="w", padx=10, pady=5)
        self.version_label = ttk.Label(self.settings_frame, text="Minecraft Version:")
        self.version_label.grid(row=1, column=0, sticky="e", padx=10, pady=5)
        self.version_var = tk.StringVar(value="1.21.4")
        self.version_menu = ttk.Combobox(self.settings_frame, textvariable=self.version_var, values=["1.21.4"], state="readonly", width=10)
        self.version_menu.grid(row=1, column=1, sticky="w", padx=10, pady=5)
        self.modpack_label = ttk.Label(self.settings_frame, text="Modpack:")
        self.modpack_label.grid(row=2, column=0, sticky="e", padx=10, pady=5)
        self.modpack_var = tk.StringVar(value="Default")
        self.modpack_menu = ttk.Combobox(self.settings_frame, textvariable=self.modpack_var, values=["Default"], state="readonly", width=20)
        self.modpack_menu.grid(row=2, column=1, sticky="w", padx=10, pady=5)
        self.modpack_menu.bind("<<ComboboxSelected>>", self.load_modpack)
        self.button_frame = ttk.Frame(self.launcher_frame)
        self.button_frame.grid(row=1, column=0, pady=20)
        self.start_button = ttk.Button(self.button_frame, text="Spiel starten", command=self.start_game, width=20)
        self.start_button.grid(row=0, column=0, padx=10)
        self.modpack_button = ttk.Button(self.button_frame, text="Modpack erstellen", command=self.create_modpack, width=20)
        self.modpack_button.grid(row=0, column=1, padx=10)
        self.add_mod_button = ttk.Button(self.button_frame, text="Mod hinzufügen", command=self.add_mod, width=20)
        self.add_mod_button.grid(row=0, column=2, padx=10)
        self.log_frame = ttk.Frame(self.launcher_frame)
        self.log_frame.grid(row=2, column=0, sticky="nsew", pady=10)
        self.launcher_frame.columnconfigure(0, weight=1)
        self.launcher_frame.rowconfigure(2, weight=1)
        self.log_text = ScrolledText(self.log_frame, height=10, font=("Consolas", 10), bg="#2e3440", fg="#d8dee9", insertbackground="#ffffff")
        self.log_text.pack(fill="both", expand=True)
        self.mods_frame = ttk.Frame(self.notebook, padding="10")
        self.notebook.add(self.mods_frame, text="Mods")
        self.mods_label = ttk.Label(self.mods_frame, text="Mod-Verwaltung kommt bald!", font=("Segoe UI", 14))
        self.mods_label.pack(pady=20)
        self.update_modpack_list()
        self.log("GlitchClient gestartet")
        self.root.after(1000, self.check_update)

    def log(self, message):
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.see(tk.END)
        logger.info(message)

    def update_modpack_list(self):
        try:
            modpacks = ["Default"] + [d.name for d in modpacks_dir.iterdir() if d.is_dir()]
            self.modpack_menu["values"] = modpacks
            logger.debug(f"Updated modpack list with {len(modpacks)} modpacks")
        except Exception as e:
            logger.error(f"Failed to update modpack list: {str(e)}")
            self.log(f"Fehler beim Aktualisieren der Modpack-Liste: {str(e)}")

    def load_modpack(self, event=None):
        global mods_dir
        selected_modpack = self.modpack_var.get()
        self.log(f"Lade Modpack: {selected_modpack}")
        logger.info(f"Loading modpack: {selected_modpack}")

        if self.temp_mods_dir:
            try:
                shutil.rmtree(self.temp_mods_dir, ignore_errors=True)
                logger.debug(f"Cleaned up temp mods directory: {self.temp_mods_dir}")
                self.temp_mods_dir = None
            except Exception as e:
                logger.error(f"Failed to clean temp mods directory: {str(e)}")

        if selected_modpack != "Default":
            try:
                self.temp_mods_dir = Path(tempfile.mkdtemp())
                modpack_path = modpacks_dir / selected_modpack
                shutil.copytree(modpack_path, self.temp_mods_dir, dirs_exist_ok=True)
                mods_dir = self.temp_mods_dir
                logger.debug(f"Created temp mods directory at {self.temp_mods_dir}")
            except Exception as e:
                logger.error(f"Failed to setup modpack: {str(e)}")
                self.log(f"Fehler beim Laden des Modpacks: {str(e)}")
                return
        else:
            mods_dir = minecraft_dir / "mods"
        self.log(f"Mod-Verzeichnis: {mods_dir}")
        logger.info(f"Using mod directory: {mods_dir}")

    def create_modpack(self):
        modpack_name = tk.simpledialog.askstring("Modpack erstellen", "Name des Modpacks:", parent=self.root)
        if modpack_name:
            try:
                modpack_path = modpacks_dir / modpack_name
                modpack_path.mkdir(exist_ok=True)
                self.log(f"Modpack erstellt: {modpack_name}")
                self.update_modpack_list()
                logger.info(f"Created new modpack: {modpack_name}")
            except Exception as e:
                logger.error(f"Failed to create modpack: {str(e)}")
                self.log(f"Fehler beim Erstellen des Modpacks: {str(e)}")

    def add_mod(self):
        try:
            files = filedialog.askopenfilenames(filetypes=[("JAR-Dateien", "*.jar")])
            if not files:
                logger.debug("Mod add operation cancelled by user")
                return
                
            for file in files:
                try:
                    dest = mods_dir / Path(file).name
                    shutil.copy(file, dest)
                    self.log(f"Mod hinzugefügt: {Path(file).name}")
                    logger.info(f"Added mod: {Path(file).name}")
                except Exception as e:
                    logger.error(f"Failed to add mod {file}: {str(e)}")
                    self.log(f"Fehler beim Hinzufügen der Mod {Path(file).name}: {str(e)}")
        except Exception as e:
            logger.error(f"Error in add_mod: {str(e)}")
            self.log(f"Fehler beim Hinzufügen von Mods: {str(e)}")

    def check_update(self):
        self.log("Prüfe auf Updates...")
        logger.info("Checking for updates")
        try:
            response = requests.get("https://download.glitchclient.theerrorexe.dev/latest.txt", timeout=5)
            latest_version = response.text.strip()
            logger.debug(f"Latest version: {latest_version}, current version: {self.current_version}")
            
            if latest_version != self.current_version:
                logger.info(f"Update available: {latest_version}")
                if messagebox.askyesno("Update verfügbar", f"Version {latest_version} ist verfügbar (aktuell: {self.current_version}). Jetzt herunterladen?"):
                    self.download_update()
            else:
                self.log("Client ist auf dem neuesten Stand")
                logger.info("Client is up to date")
        except requests.RequestException as e:
            logger.error(f"Update check failed: {str(e)}")
            self.log(f"Fehler beim Update-Check: {str(e)}")
        except Exception as e:
            logger.error(f"Unexpected error in update check: {str(e)}")
            self.log(f"Unerwarteter Fehler beim Update-Check: {str(e)}")

    def download_update(self):
        self.log("Lade Update herunter...")
        logger.info("Downloading update")
        try:
            update_path = cache_dir / f"glitchclient_new_{platform.system().lower()}"
            urllib.request.urlretrieve(client_url, update_path)
            self.log("Update heruntergeladen. Bitte Client neu starten.")
            logger.info(f"Update downloaded to {update_path}")
            
            if platform.system() == "Windows":
                subprocess.run(["start", str(update_path)], shell=True)
            else:
                subprocess.run(["chmod", "+x", str(update_path)])
                subprocess.run([str(update_path)])
            self.root.quit()
        except urllib.error.URLError as e:
            logger.error(f"Failed to download update: {str(e)}")
            self.log(f"Fehler beim Herunterladen des Updates: {str(e)}")
        except subprocess.CalledProcessError as e:
            logger.error(f"Failed to execute update: {str(e)}")
            self.log(f"Fehler beim Ausführen des Updates: {str(e)}")
        except Exception as e:
            logger.error(f"Unexpected error during update: {str(e)}")
            self.log(f"Unerwarteter Fehler während des Updates: {str(e)}")

    def start_game(self):
        global ram, version_choice
        ram = self.ram_var.get()
        version_choice = self.version_var.get()
        self.log("Starte Minecraft...")
        logger.info(f"Starting Minecraft with RAM: {ram}, version: {version_choice}")
        self.start_button.config(state="disabled")
        threading.Thread(target=self.run_game, daemon=True).start()

    def update_user_info(self, username, uuid_str):
        self.user_label.config(text=username)
        logger.debug(f"Updating user info for {username}")
        try:
            head_url = f"https://crafthead.net/avatar/{uuid_str}/32"
            response = requests.get(head_url, timeout=5)
            img = Image.open(io.BytesIO(response.content)).resize((32, 32), Image.LANCZOS)
            self.head_photo = ImageTk.PhotoImage(img)
            self.head_label.config(image=self.head_photo)
            logger.debug("Successfully updated user head")
        except requests.RequestException as e:
            logger.error(f"Failed to fetch player head: {str(e)}")
        except Exception as e:
            logger.error(f"Error updating user info: {str(e)}")

    def run_game(self):
        try:
            logger.info("Running game in background thread")
            main(self)
            self.log("Spiel erfolgreich gestartet")
            logger.info("Game started successfully")
        except Exception as e:
            logger.error(f"Game failed to start: {str(e)}", exc_info=True)
            self.log(f"Fehler: {str(e)}")
        finally:
            self.root.after(0, lambda: self.start_button.config(state="normal"))

def check_and_install_java():
    global java_path
    java_dir = minecraft_dir / "java"
    java_dir.mkdir(parents=True, exist_ok=True)
    
    def check_java_version(java_executable):
        try:
            result = subprocess.run([java_executable, "-version"], capture_output=True, text=True)
            version_line = result.stderr.splitlines()[0]
            if "version" in version_line:
                version_str = version_line.split()[2].replace('"', '')
                major_version = int(version_str.split('.')[0])
                return major_version >= 21
        except:
            return False
        return False
    
    system = platform.system().lower()
    if system == "windows":
        java_bin = java_dir / "bin" / "java.exe"
    else:
        java_bin = java_dir / "bin" / "java"
    
    if java_bin.exists():
        if check_java_version(str(java_bin)):
            java_path = str(java_bin)
            logger.info(f"Lokale Java 21+ Installation gefunden unter: {java_path}")
            return True
        else:
            logger.warning("Lokale Java-Installation veraltet oder defekt, wird neu installiert")
            shutil.rmtree(java_dir)
            java_dir.mkdir(parents=True, exist_ok=True)
    
    logger.info("Java 21 wird installiert...")
    
    java_urls = {
        "windows": "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_x64_windows_hotspot_21.0.2_13.zip",
        "linux": "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_x64_linux_hotspot_21.0.2_13.tar.gz",
        "darwin": "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_x64_mac_hotspot_21.0.2_13.tar.gz"
    }
    
    if system not in java_urls:
        logger.error(f"Nicht unterstütztes System: {system}")
        return False
    
    try:
        java_archive = cache_dir / f"java21.{'zip' if system == 'windows' else 'tar.gz'}"
        logger.info(f"Lade Java 21 herunter von {java_urls[system]}")
        urllib.request.urlretrieve(java_urls[system], java_archive)
        logger.info(f"Entpacke Java nach {java_dir}")
        if system == "windows":
            with zipfile.ZipFile(java_archive, 'r') as zip_ref:
                zip_ref.extractall(java_dir)
        else:
            with tarfile.open(java_archive, 'r:gz') as tar_ref:
                tar_ref.extractall(java_dir)
        jdk_folder = next(java_dir.glob("jdk*"))
        for item in jdk_folder.iterdir():
            dest = java_dir / item.name
            if dest.exists():
                if dest.is_dir():
                    shutil.rmtree(dest)
                else:
                    dest.unlink()
            shutil.move(str(item), str(java_dir))
        jdk_folder.rmdir()
        java_path = str(java_bin)
        logger.info(f"Java 21 erfolgreich installiert unter: {java_path}")
        if system != "windows":
            os.chmod(java_bin, 0o755)
        if check_java_version(java_path):
            logger.info("Java 21 Installation erfolgreich verifiziert")
            return True
        else:
            logger.error("Die Java 21 Installation funktioniert nicht korrekt")
            return False
            
    except Exception as e:
        logger.error(f"Fehler bei der Java-Installation: {str(e)}")
        return False
    finally:
        if 'java_archive' in locals() and java_archive.exists():
            try:
                java_archive.unlink()
            except:
                pass

def add_blockattack_server():
    servers_file = minecraft_dir / "servers.dat"
    server_entry = {
        "name": "Blockattack",
        "ip": "blockattack.fun",
        "icon": None,
        "acceptTextures": True
    }
    logger.debug("Adding Blockattack server to servers.dat")

    servers = {"servers": []}
    if servers_file.exists():
        try:
            with open(servers_file, "r") as f:
                servers = json.load(f)
            logger.debug("Loaded existing servers.dat")
        except json.JSONDecodeError as e:
            logger.warning(f"Failed to parse servers.dat: {str(e)}")

    if not any(srv["ip"] == "blockattack.fun" for srv in servers["servers"]):
        servers["servers"].append(server_entry)
        try:
            with open(servers_file, "w") as f:
                json.dump(servers, f)
            logger.info("Added Blockattack server to servers.dat")
        except Exception as e:
            logger.error(f"Failed to save servers.dat: {str(e)}")

def authenticate_with_microsoft():
    logger.info("Starting Microsoft authentication")
    
    def perform_authentication():
        state = uuid.uuid4().hex
        auth_url = f"{AUTHORITY}?client_id={CLIENT_ID}&response_type=code&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={state}&prompt={PROMPT}&response_mode=query"
        logger.debug(f"Opening auth URL in browser: {auth_url}")
        webbrowser.open(auth_url)

        class OAuth2Handler(http.server.SimpleHTTPRequestHandler):
            def do_GET(self):
                parsed = urlparse(self.path)
                params = parse_qs(parsed.query)
                code = params.get("code", [None])[0]
                received_state = params.get("state", [None])[0]
                
                logger.debug(f"Received callback with code: {code is not None}, state match: {received_state == state}")
                
                if code and received_state == state:
                    self.server.auth_code = code
                    self.send_response(200)
                    self.end_headers()
                    self.wfile.write(b"<h1>Authentifizierung erfolgreich!</h1><p>Du kannst dieses Fenster jetzt schliessen.</p>")
                    threading.Thread(target=self.server.shutdown, daemon=True).start()
                else:
                    self.send_response(400)
                    self.end_headers()
                    self.wfile.write(b"<h1>Fehler:</h1><p>Kein Code oder falscher State erhalten.</p>")
                    
            def log_message(self, format, *args):
                logger.debug(f"HTTP {args[0]} {args[1]} {args[2]}")

        class AuthServer(socketserver.TCPServer):
            allow_reuse_address = True
            auth_code = None

        with AuthServer(("localhost", 8086), OAuth2Handler) as server:
            logger.debug("Starting auth server on port 8086")
            threading.Thread(target=server.serve_forever, daemon=True).start()
            while server.auth_code is None:
                pass

            logger.debug(f"Received auth code: {server.auth_code}")
            data = {
                "client_id": CLIENT_ID,
                "grant_type": "authorization_code",
                "code": server.auth_code,
                "redirect_uri": REDIRECT_URI,
                "scope": SCOPE,
            }
            
            logger.debug("Requesting access token from Microsoft")
            token_response = requests.post(TOKEN_URL, data=data, timeout=10)
            token_response.raise_for_status()
            token_json = token_response.json()
            
            if "access_token" not in token_json:
                error_msg = token_json.get('error_description', 'Unbekannter Fehler')
                logger.error(f"Token request failed: {error_msg}")
                raise ValueError(f"Fehler beim Token-Abruf: {error_msg}")
                
            logger.debug("Successfully obtained access token")
            with open(token_file, "w") as f:
                json.dump(token_json, f)
            return token_json["access_token"]

    if token_file.exists():
        try:
            with open(token_file) as f:
                cached_token = json.load(f)
            if "access_token" in cached_token:
                logger.debug("Found cached access token, validating")
                xbl_data = {
                    "Properties": {"AuthMethod": "RPS", "SiteName": "user.auth.xboxlive.com", "RpsTicket": f"d={cached_token['access_token']}"},
                    "RelyingParty": "http://auth.xboxlive.com",
                    "TokenType": "JWT"
                }
                xbl_response = requests.post(XBOX_LIVE_AUTH_URL, json=xbl_data, timeout=10)
                xbl_response.raise_for_status()
                logger.debug("Cached token is still valid")
                return cached_token["access_token"]
        except Exception as e:
            logger.warning(f"Cached token invalid: {str(e)}")
            token_file.unlink()
            
    return perform_authentication()

def copy_from_original_minecraft():
    original_assets = minecraft_launcher_dir / "assets"
    if original_assets.exists() and not any(assets_dir.iterdir()):
        logger.info(f"Copying assets from original Minecraft at {original_assets}")
        try:
            shutil.copytree(original_assets, assets_dir, dirs_exist_ok=True)
            logger.debug("Assets copied successfully")
        except Exception as e:
            logger.error(f"Failed to copy assets: {str(e)}")

def get_minecraft_version_manifest():
    logger.info("Fetching Minecraft version manifest")
    try:
        response = requests.get("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json", timeout=10)
        response.raise_for_status()
        manifest = response.json()
        logger.debug(f"Found {len(manifest['versions'])} versions in manifest")
        return manifest
    except requests.RequestException as e:
        logger.error(f"Failed to fetch version manifest: {str(e)}")
        raise

def download_minecraft_client(version_choice):
    target_jar = versions_dir / version_choice / f"{version_choice}.jar"
    target_json = versions_dir / version_choice / f"{version_choice}.json"
    logger.info(f"Downloading Minecraft client for version {version_choice}")

    if not target_jar.exists() or target_jar.stat().st_size == 0:
        manifest = get_minecraft_version_manifest()
        for version in manifest["versions"]:
            if version["id"] == version_choice:
                logger.debug(f"Found version {version_choice} in manifest")
                version_data = requests.get(version["url"], timeout=10).json()
                client_url = version_data["downloads"]["client"]["url"]
                
                logger.debug(f"Downloading client from {client_url}")
                target_jar.parent.mkdir(parents=True, exist_ok=True)
                urllib.request.urlretrieve(client_url, target_jar)
                
                if not target_jar.exists() or target_jar.stat().st_size == 0:
                    logger.error("Downloaded client JAR is empty")
                    raise ValueError(f"Fehler beim Herunterladen der Minecraft Client JAR für {version_choice}")
                    
                with open(target_json, 'w') as f:
                    json.dump(version_data, f)
                logger.info(f"Successfully downloaded client for {version_choice}")
                return version_data
                
        logger.error(f"Version {version_choice} not found in manifest")
        raise ValueError(f"Konnte Version {version_choice} nicht in der Version-Manifest finden")

    if target_json.exists():
        logger.debug(f"Using cached client for {version_choice}")
        return json.loads(target_json.read_text())
        
    logger.error(f"Missing JSON for version {version_choice}")
    raise ValueError(f"Konnte Version {version_choice} JSON nicht finden")

def ensure_mixin_library():
    mixin_version = "0.15.4+mixin.0.8.7"
    mixin_path = libraries_dir / f"net/fabricmc/sponge-mixin/{mixin_version}/sponge-mixin-{mixin_version}.jar"
    
    if not mixin_path.exists():
        logger.info(f"Downloading Mixin library version {mixin_version}")
        try:
            mixin_path.parent.mkdir(parents=True, exist_ok=True)
            urllib.request.urlretrieve(
                f"https://maven.fabricmc.net/net/fabricmc/sponge-mixin/{mixin_version}/sponge-mixin-{mixin_version}.jar",
                mixin_path
            )
            logger.debug(f"Mixin library downloaded to {mixin_path}")
        except Exception as e:
            logger.error(f"Failed to download Mixin library: {str(e)}")
            raise
            
    return mixin_path

def ensure_asm_libraries():
    asm_version = "9.7.1"
    asm_libraries = [
        f"org/ow2/asm/asm/{asm_version}/asm-{asm_version}.jar",
        f"org/ow2/asm/asm-analysis/{asm_version}/asm-analysis-{asm_version}.jar",
        f"org/ow2/asm/asm-commons/{asm_version}/asm-commons-{asm_version}.jar",
        f"org/ow2/asm/asm-tree/{asm_version}/asm-tree-{asm_version}.jar",
        f"org/ow2/asm/asm-util/{asm_version}/asm-util-{asm_version}.jar"
    ]
    logger.info("Ensuring ASM libraries are available")
    
    for lib_path in asm_libraries:
        full_path = libraries_dir / lib_path
        if not full_path.exists():
            try:
                full_path.parent.mkdir(parents=True, exist_ok=True)
                urllib.request.urlretrieve(f"https://repo1.maven.org/maven2/{lib_path}", full_path)
                logger.debug(f"Downloaded ASM library: {lib_path}")
            except Exception as e:
                logger.error(f"Failed to download ASM library {lib_path}: {str(e)}")
                raise

def ensure_intermediary_mappings(version_choice):
    intermediary_path = libraries_dir / f"net/fabricmc/intermediary/{version_choice}/intermediary-{version_choice}-v2.jar"
    
    if not intermediary_path.exists():
        logger.info(f"Downloading intermediary mappings for {version_choice}")
        try:
            intermediary_url = f"https://maven.fabricmc.net/net/fabricmc/intermediary/{version_choice}/intermediary-{version_choice}-v2.jar"
            intermediary_path.parent.mkdir(parents=True, exist_ok=True)
            urllib.request.urlretrieve(intermediary_url, intermediary_path)
            
            if not intermediary_path.exists() or intermediary_path.stat().st_size == 0:
                logger.error("Downloaded intermediary mappings are empty")
                raise ValueError(f"Fehler beim Herunterladen der Intermediary Mappings für {version_choice}")
                
            logger.debug(f"Intermediary mappings downloaded to {intermediary_path}")
        except Exception as e:
            logger.error(f"Failed to download intermediary mappings: {str(e)}")
            raise
            
    return intermediary_path

def install_fabric(version_choice):
    logger.info(f"Installing Fabric for Minecraft {version_choice}")
    
    try:
        response = requests.get("https://theerrorexe.dev/client/newest.fabric.txt", timeout=5)
        installer_version = response.text.strip() if response.status_code == 200 else "1.0.3"
        logger.debug(f"Using Fabric installer version: {installer_version}")
    except Exception as e:
        logger.warning(f"Couldn't fetch latest Fabric installer version, using default: {str(e)}")
        installer_version = "1.0.3"

    try:
        response = requests.get(f"https://meta.fabricmc.net/v2/versions/loader/{version_choice}", timeout=10)
        response.raise_for_status()
        loader_info = response.json()[0]
        loader_version = loader_info["loader"]["version"]
        logger.debug(f"Found Fabric loader version: {loader_version}")
    except Exception as e:
        logger.error(f"Failed to get Fabric loader version: {str(e)}")
        raise

    fabric_client_jar_dir = versions_dir / f"fabric-loader-{loader_version}-{version_choice}"
    fabric_client_jar_path = fabric_client_jar_dir / f"fabric-loader-{loader_version}.jar"

    if not fabric_client_jar_path.exists():
        try:
            fabric_client_jar_dir.mkdir(parents=True, exist_ok=True)
            urllib.request.urlretrieve(
                f"https://maven.fabricmc.net/net/fabricmc/fabric-loader/{loader_version}/fabric-loader-{loader_version}.jar",
                fabric_client_jar_path
            )
            logger.debug(f"Downloaded Fabric loader to {fabric_client_jar_path}")
        except Exception as e:
            logger.error(f"Failed to download Fabric loader: {str(e)}")
            raise

    installer_path = cache_dir / "fabric-installer.jar"
    if not installer_path.exists():
        try:
            urllib.request.urlretrieve(
                f"https://maven.fabricmc.net/net/fabricmc/fabric-installer/{installer_version}/fabric-installer-{installer_version}.jar",
                installer_path
            )
            logger.debug(f"Downloaded Fabric installer to {installer_path}")
        except Exception as e:
            logger.error(f"Failed to download Fabric installer: {str(e)}")
            raise

    try:
        logger.info("Running Fabric installer")
        subprocess.run([
            java_path, "-jar", str(installer_path), "client",
            "-dir", str(minecraft_dir), "-mcversion", version_choice, "-noprofile"
        ], check=True)
        logger.info("Fabric installed successfully")
    except subprocess.CalledProcessError as e:
        logger.error(f"Fabric installation failed: {str(e)}")
        raise

    return version_choice, loader_version

def clean_duplicate_libraries():
    logger.info("Cleaning duplicate libraries")
    problem_libs = {
        "org/ow2/asm/asm": ["9.6", "9.5", "9.4", "9.3", "9.2", "9.1", "9.7"],
        "net/fabricmc/sponge-mixin": ["0.12.5+mixin.0.8.5", "0.15.3+mixin.0.8.7"],
        "org/ow2/asm/asm-analysis": ["9.6", "9.5", "9.7"],
        "org/ow2/asm/asm-commons": ["9.6", "9.5", "9.7"],
        "org/ow2/asm/asm-tree": ["9.6", "9.5", "9.7"],
        "org/ow2/asm/asm-util": ["9.6", "9.5", "9.7"],
        "org/slf4j/slf4j-api": ["1.7.36", "1.8.0"],
        "org/apache/logging/log4j/log4j-core": ["2.17.2", "2.17.1"],
        "net/fabricmc/fabric-loader": ["0.15.7", "0.16.9", "0.16.10"],
        "net/minecraftforge/ForgeAutoRenamingTool": ["0.1.22"],
    }
    
    for lib_base, versions_to_remove in problem_libs.items():
        for version in versions_to_remove:
            lib_path = libraries_dir / f"{lib_base}/{version}"
            if lib_path.exists():
                try:
                    shutil.rmtree(lib_path, ignore_errors=True)
                    logger.debug(f"Removed duplicate library: {lib_path}")
                except Exception as e:
                    logger.warning(f"Failed to remove duplicate library {lib_path}: {str(e)}")

def download_mods(version_choice):
    mods = [
        ("https://cdn.modrinth.com/data/AANobbMI/versions/c3YkZvne/sodium-fabric-0.6.13%2Bmc1.21.4.jar", "sodium.jar"),
        ("https://cdn.modrinth.com/data/P7dR8mSH/versions/bQZpGIz0/fabric-api-0.119.2%2B1.21.4.jar", "fabric-api.jar"),
        ("https://cdn.modrinth.com/data/9eGKb6K1/versions/OkKjbu1V/voicechat-fabric-1.21.4-2.5.29.jar", "voicechat.jar"),
        ("https://cdn.modrinth.com/data/mOgUt4GM/versions/7iGb2ltH/modmenu-13.0.3.jar", "modmenu.jar"),
        ("https://cdn.modrinth.com/data/YL57xq9U/versions/Ca054sTe/iris-fabric-1.8.8%2Bmc1.21.4.jar", "iris.jar"),
        ("https://cdn.modrinth.com/data/1bokaNcj/versions/tYtr1STe/Xaeros_Minimap_25.2.0_Fabric_1.21.4.jar", "xaeros-minimap.jar"),
        ("https://cdn.modrinth.com/data/NcUtCpym/versions/fh7IHLqi/XaerosWorldMap_1.39.4_Fabric_1.21.4.jar", "xaeros-worldmap.jar"),
        ("https://download.glitchclient.theerrorexe.dev/glitchbrand-1.0.jar", "glitchclientbrandhelper.jar")
    ]
    logger.info(f"Downloading mods for version {version_choice}")
    
    for url, name in mods:
        dest = mods_dir / name
        if dest.exists():
            try:
                dest.unlink()
                logger.debug(f"Removed existing mod file: {dest}")
            except Exception as e:
                logger.warning(f"Failed to remove existing mod {dest}: {str(e)}")
                
        try:
            urllib.request.urlretrieve(url, dest)
            if not dest.exists() or dest.stat().st_size == 0:
                logger.error(f"Downloaded mod {name} is empty")
                raise ValueError(f"Mod {name} konnte nicht korrekt heruntergeladen werden")
            logger.debug(f"Downloaded mod: {name}")
        except Exception as e:
            logger.error(f"Failed to download mod {name}: {str(e)}")
            raise

def verify_fabric_api():
    fabric_api_jar = mods_dir / "fabric-api.jar"
    if fabric_api_jar.exists():
        try:
            with zipfile.ZipFile(fabric_api_jar, 'r') as jar:
                with jar.open('fabric.mod.json') as f:
                    mod_info = json.load(f)
                    if mod_info.get('version') != '0.119.2+1.21.4':
                        logger.error(f"Invalid Fabric API version: {mod_info.get('version')}")
                        raise ValueError("Ungültige Fabric API-Version")
                    logger.debug("Fabric API version verified")
        except Exception as e:
            logger.error(f"Failed to verify Fabric API: {str(e)}")
            raise

def download_libraries(version_data):
    logger.info("Downloading required libraries")
    for lib in version_data.get("libraries", []):
        if "downloads" in lib and "artifact" in lib["downloads"]:
            artifact = lib["downloads"]["artifact"]
            lib_path = libraries_dir / artifact["path"]
            if not lib_path.exists():
                try:
                    lib_path.parent.mkdir(parents=True, exist_ok=True)
                    urllib.request.urlretrieve(artifact["url"], lib_path)
                    logger.debug(f"Downloaded library: {lib_path.name}")
                except Exception as e:
                    logger.error(f"Failed to download library {artifact['url']}: {str(e)}")
                    raise

def build_classpath(version_data, loader_version, version_choice):
    logger.info("Building classpath")
    ensure_asm_libraries()
    ensure_mixin_library()
    ensure_intermediary_mappings(version_choice)

    library_jars = []
    for root, _, files in os.walk(libraries_dir):
        for file in files:
            if file.endswith('.jar'):
                library_jars.append(str(Path(root) / file))

    classpath = [
        str(versions_dir / version_choice / f"{version_choice}.jar"),
        str(libraries_dir / f"net/fabricmc/fabric-loader/{loader_version}/fabric-loader-{loader_version}.jar"),
        *library_jars
    ]

    classpath_file = cache_dir / "classpath.txt"
    try:
        with open(classpath_file, "w", encoding="utf-8") as f:
            f.write(os.pathsep.join(classpath))
        logger.debug(f"Classpath written to {classpath_file}")
        logger.debug(f"Classpath contents:\n{os.pathsep.join(classpath)}")
    except Exception as e:
        logger.error(f"Failed to write classpath file: {str(e)}")
        raise
        
    return str(classpath_file)

def get_launch_command(classpath_file, username, uuid_str, access_token, assets_index, version_id, gui):
    command = [
        java_path,
        f"-Xmx{ram}",
        f"-Xms{ram}",
        "-XX:+UseG1GC",
        "-Dlog4j2.formatMsgNoLookups=true",
        "-Dfabric.log.level=debug",
        "-cp", f"@{classpath_file}",
        "net.fabricmc.loader.impl.launch.knot.KnotClient",
        "--username", username,
        "--version", version_id,
        "--gameDir", str(minecraft_dir),
        "--assetsDir", str(assets_dir),
        "--assetIndex", assets_index,
        "--uuid", uuid_str,
        "--accessToken", access_token,
        "--userType", "msa",
        "--versionType", "Glitchclient"
    ]
    logger.debug(f"Launch command: {' '.join(command)}")

    log_file = minecraft_dir / "logs" / "latest.log"
    log_file.parent.mkdir(parents=True, exist_ok=True)

    def follow_log():
        try:
            logger.debug("Starting log follower")
            with open(log_file, "r", encoding="utf-8") as f:
                f.seek(0, os.SEEK_END)
                while True:
                    line = f.readline()
                    if not line:
                        if gui.start_button["state"] == "disabled":
                            threading.Event().wait(0.1)
                            continue
                        break
                    gui.log(line.strip())
        except Exception as e:
            logger.error(f"Log follower error: {str(e)}")
            gui.log(f"Fehler beim Lesen der Logs: {str(e)}")

    threading.Thread(target=follow_log, daemon=True).start()
    return command

def main(gui):
    try:
        logger.info("Starting main game launch process")
        check_and_install_java()

        if not first_run_file.exists():
            logger.info("First run detected")
            add_blockattack_server()
            first_run_file.touch()
            gui.log("Blockattack-Server (blockattack.fun) hinzugefügt")

        copy_from_original_minecraft()
        logger.info("Starting Microsoft authentication")
        ms_access_token = authenticate_with_microsoft()

        logger.info("Authenticating with Xbox Live")
        xbl_response = requests.post(
            XBOX_LIVE_AUTH_URL,
            json={
                "Properties": {"AuthMethod": "RPS", "SiteName": "user.auth.xboxlive.com", "RpsTicket": f"d={ms_access_token}"},
                "RelyingParty": "http://auth.xboxlive.com",
                "TokenType": "JWT"
            },
            timeout=10
        )
        xbl_response.raise_for_status()
        xbl_json = xbl_response.json()
        xbl_token = xbl_json["Token"]
        user_hash = xbl_json["DisplayClaims"]["xui"][0]["uhs"]
        logger.debug("Xbox Live authentication successful")

        logger.info("Authenticating with XSTS")
        xsts_response = requests.post(
            XSTS_AUTH_URL,
            json={
                "Properties": {"SandboxId": "RETAIL", "UserTokens": [xbl_token]},
                "RelyingParty": "rp://api.minecraftservices.com/",
                "TokenType": "JWT"
            },
            timeout=10
        )
        xsts_response.raise_for_status()
        xsts_json = xsts_response.json()
        xsts_token = xsts_json["Token"]
        logger.debug("XSTS authentication successful")

        logger.info("Authenticating with Minecraft services")
        minecraft_response = requests.post(
            MINECRAFT_AUTH_URL,
            json={"identityToken": f"XBL3.0 x={user_hash};{xsts_token}"},
            timeout=10
        )
        minecraft_response.raise_for_status()
        minecraft_access_token = minecraft_response.json()["access_token"]
        logger.debug("Minecraft authentication successful")

        logger.info("Checking Minecraft entitlements")
        entitlements_response = requests.get(
            MINECRAFT_ENTITLEMENTS_URL,
            headers={"Authorization": f"Bearer {minecraft_access_token}"},
            timeout=10
        )
        entitlements_response.raise_for_status()
        if not any(item['name'] == 'game_minecraft' for item in entitlements_response.json()['items']):
            logger.error("User doesn't own Minecraft")
            raise ValueError("Du besitzt das Spiel Minecraft nicht.")

        logger.info("Fetching Minecraft profile")
        profile_response = requests.get(
            MINECRAFT_PROFILE_URL,
            headers={"Authorization": f"Bearer {minecraft_access_token}"},
            timeout=10
        )
        profile_response.raise_for_status()
        profile_json = profile_response.json()
        username = profile_json["name"]
        uuid_str = profile_json["id"]
        logger.info(f"Authenticated as {username} ({uuid_str})")

        gui.update_user_info(username, uuid_str)
        version_data = download_minecraft_client(version_choice)
        download_libraries(version_data)
        version_choice_local, loader_version = install_fabric(version_choice)
        download_mods(version_choice)
        verify_fabric_api()
        clean_duplicate_libraries()

        classpath_file = build_classpath(version_data, loader_version, version_choice)
        command = get_launch_command(
            classpath_file,
            username,
            uuid_str,
            minecraft_access_token,
            version_data["assetIndex"]["id"],
            f"fabric-loader-{loader_version}-{version_choice}",
            gui
        )

        logger.info("Launching Minecraft")
        subprocess.run(command, check=True)
        logger.info("Minecraft process finished")
    except requests.RequestException as e:
        logger.error(f"HTTP request failed: {str(e)}")
        gui.log(f"Netzwerkfehler: {str(e)}")
    except subprocess.CalledProcessError as e:
        logger.error(f"Subprocess failed: {str(e)}")
        gui.log(f"Prozessfehler: {str(e)}")
    except Exception as e:
        logger.error(f"Unexpected error: {str(e)}", exc_info=True)
        gui.log(f"Unerwarteter Fehler: {str(e)}")

if __name__ == "__main__":
    try:
        logger.info("Starting GlitchClient application")
        root = tk.Tk()
        app = GlitchClientGUI(root)
        root.mainloop()
    except Exception as e:
        logger.critical(f"Application crashed: {str(e)}", exc_info=True)
    finally:
        if hasattr(app, 'temp_mods_dir') and app.temp_mods_dir:
            try:
                shutil.rmtree(app.temp_mods_dir, ignore_errors=True)
                logger.debug("Cleaned up temp mods directory")
            except Exception as e:
                logger.warning(f"Failed to clean temp mods directory: {str(e)}")
        logger.info("Application shutdown")
