"""SweetCheat Desktop Application — 2027 Edition v0.9.16-Slayer"""
import sys
import os
import json
import time
import threading
import urllib.request
import urllib.error
import ssl
import struct
from process_scanner import ProcessScanner
from collections import Counter

ssl._create_default_https_context = ssl._create_unverified_context

APP_VERSION = '0.9.16-Slayer'
CONFIG_DIR = os.path.join(os.environ.get('APPDATA', os.path.expanduser('~')), 'SweetCheat')
CONFIG_FILE = os.path.join(CONFIG_DIR, 'config.json')
API_BASE = os.environ.get('TRAINERHUB_API', 'https://sayfespace.online/trainerhub/api')
os.makedirs(CONFIG_DIR, exist_ok=True)

try:
    import tkinter as tk
    from tkinter import ttk, messagebox, scrolledtext, simpledialog
except ImportError:
    print("tkinter fehlt"); sys.exit(1)

from ui_components import (
    ModernStyle, RoundedFrame, AnimatedButton, StatusBadge, ModernCombobox,
    card, section_title, primary_btn, secondary_btn, ghost_btn, badge, stat_card, input_field
)

WINDOWS = sys.platform == 'win32'
pymem = None
if WINDOWS:
    try:
        import pymem
        from pymem import Pymem
    except ImportError:
        pymem = None

def try_import(mod):
    try:
        __import__(mod)
        return True
    except ImportError:
        return False

SDV_SAVE = try_import('sdv_savegame')
BRIDGE = try_import('stardew_bridge')
PATTERN_LEARNER = try_import('pattern_learner')

def load_config():
    try:
        with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception:
        return {'api_key': None, 'api_base': API_BASE, 'favorites': [], 'recent_games': []}

def save_config(cfg):
    try:
        with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
            json.dump(cfg, f)
    except Exception as e:
        print(f"Config save error: {e}")

class SweetCheatGUI:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title(f"SweetCheat Engine {APP_VERSION}")
        self.root.geometry("1280x820")
        self.root.minsize(1100, 700)
        self.root.configure(bg=ModernStyle.BG)
        ModernStyle.apply(self.root)
        self.setup_ttk_styles()
        self.active_toggles = {}
        self.api_key = ''
        self.api_base = API_BASE
        self.user = None
        self.games, self.trainers, self.current_game, self.current_game_pid = [], [], None, None
        self.recent_games = []
        self.trainer_toggle_buttons = {}
        self.config = load_config()
        self.api_key = self.config.get('api_key')
        self.api_base = self.config.get('api_base', API_BASE)
        self.premium_data, self.user_info = {}, {}
        self.favorites = set(self.config.get('favorites', []))
        self.recent_games = list(self.config.get('recent_games', []))
        self.log_lines = []
        self.bridge_client = None
        self.hotkey_manager = None

        self.build_ui()
        self.api_key and self.validate_key() or self.show_login()
        self.start_background_tasks()

    # ---------- UI BUILD ----------
    def build_ui(self):
        self.header = tk.Frame(self.root, bg=ModernStyle.BG, height=64)
        self.header.pack(fill='x', padx=22, pady=(16,0))
        self.header.pack_propagate(False)

        self.brand = tk.Label(self.header, text="SweetCheat", font=ModernStyle.FONT_HEAD,
                              bg=ModernStyle.BG, fg=ModernStyle.ACCENT)
        self.brand.pack(side='left')
        self.subbrand = tk.Label(self.header, text="ENGINE", font=('Rajdhani', 14, 'bold'),
                                 bg=ModernStyle.BG, fg=ModernStyle.ACCENT_SEC)
        self.subbrand.pack(side='left', padx=(6,0), pady=(10,0))

        self.status_frame = tk.Frame(self.header, bg=ModernStyle.BG)
        self.status_frame.pack(side='right')

        self.premium_badge = StatusBadge(self.status_frame, "FREE", ModernStyle.TEXT_MUTED)
        self.premium_badge.pack(side='left', padx=(0,10))

        self.admin_btn = tk.Button(self.status_frame, text="🛡️ ADMIN", bg=ModernStyle.DANGER, fg="#fff",
                                   relief='flat', font=ModernStyle.FONT_BODY_B, padx=14, pady=5,
                                   command=self.show_admin_panel, borderwidth=0, cursor='hand2')
        self.admin_btn.pack(side='left', padx=(0,8))
        self.admin_btn.pack_forget()

        self.connection_badge = StatusBadge(self.status_frame, "● OFFLINE", ModernStyle.DANGER)
        self.connection_badge.pack(side='left', padx=(0,12))

        ghost_btn(self.status_frame, "⚙", self.show_settings).pack(side='left', padx=(0,8))
        ghost_btn(self.status_frame, "Account", self.show_account).pack(side='left', padx=(0,8))
        ghost_btn(self.status_frame, "Logout", self.logout).pack(side='left')

        self.content = tk.Frame(self.root, bg=ModernStyle.BG)
        self.content.pack(fill='both', expand=True, padx=22, pady=22)

        self.statusbar = tk.Frame(self.root, bg=ModernStyle.BG_CARD, height=30)
        self.statusbar.pack(fill='x', side='bottom')
        self.statusbar.pack_propagate(False)
        self.status_text = tk.Label(self.statusbar, text="Bereit", bg=ModernStyle.BG_CARD,
                                    fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_SMALL)
        self.status_text.pack(side='left', padx=14, pady=5)
        tk.Label(self.statusbar, text=f"v{APP_VERSION}", bg=ModernStyle.BG_CARD,
                 fg=ModernStyle.ACCENT, font=ModernStyle.FONT_SMALL).pack(side='right', padx=14, pady=5)

    def clear_content(self):
        for w in self.content.winfo_children(): w.destroy()

    def setup_ttk_styles(self):
        s = ttk.Style(self.root)
        s.theme_use('clam')
        s.configure('Modern.TCombobox',
                    fieldbackground=ModernStyle.BG_INPUT,
                    background=ModernStyle.BG_CARD,
                    foreground=ModernStyle.TEXT,
                    arrowcolor=ModernStyle.ACCENT,
                    bordercolor=ModernStyle.BORDER,
                    darkcolor=ModernStyle.BG_CARD,
                    lightcolor=ModernStyle.BG_CARD,
                    selectbackground=ModernStyle.ACCENT,
                    selectforeground=ModernStyle.BG,
                    padding=8)
        s.map('Modern.TCombobox',
              fieldbackground=[('readonly', ModernStyle.BG_INPUT), ('active', ModernStyle.BG_INPUT)],
              selectbackground=[('readonly', ModernStyle.ACCENT)],
              foreground=[('readonly', ModernStyle.TEXT)])
        self.root.option_add('*TCombobox*Listbox.background', ModernStyle.BG_INPUT)
        self.root.option_add('*TCombobox*Listbox.foreground', ModernStyle.TEXT)
        self.root.option_add('*TCombobox*Listbox.selectBackground', ModernStyle.ACCENT)
        self.root.option_add('*TCombobox*Listbox.selectForeground', ModernStyle.BG)

    # ---------- LOGIN ----------
    def show_login(self):
        self.clear_content()
        wrapper = tk.Frame(self.content, bg=ModernStyle.BG)
        wrapper.place(relx=0.5, rely=0.5, anchor='center')
        c = RoundedFrame(wrapper, radius=20, bg=ModernStyle.BG_CARD, border_color=ModernStyle.BORDER)
        c.pack(padx=2, pady=2)
        c.inner.configure(padx=55, pady=45)

        tk.Label(c.inner, text="SWEETCHEAT", font=('Rajdhani', 32, 'bold'),
                 bg=ModernStyle.BG_CARD, fg=ModernStyle.ACCENT).pack()
        tk.Label(c.inner, text="ENGINE", font=('Rajdhani', 16, 'bold'),
                 bg=ModernStyle.BG_CARD, fg=ModernStyle.ACCENT_SEC).pack()
        tk.Label(c.inner, text="Singleplayer Trainer für 300+ Spiele", font=ModernStyle.FONT_BODY,
                 bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT_MUTED).pack(pady=(6,35))

        self.login_user = input_field(c.inner, "Benutzername oder E-Mail", width=44)
        self.login_pass = input_field(c.inner, "Passwort", show='*', width=44)
        self.login_pass.bind('<Return>', lambda e: self.do_login())

        AnimatedButton(c.inner, text="Einloggen", command=self.do_login, width=360, height=46).pack(fill='x', pady=(8,0))
        self.login_msg = tk.Label(c.inner, text="", bg=ModernStyle.BG_CARD, fg=ModernStyle.DANGER, font=ModernStyle.FONT_BODY)
        self.login_msg.pack(pady=(18,0))
        ghost_btn(c.inner, "Noch kein Account? Registrieren", lambda: None).pack(pady=(20,0))

        self.login_user.focus_set()

    def do_login(self):
        u, p = self.login_user.get().strip(), self.login_pass.get()
        if not u or not p:
            self.login_msg.config(text="Bitte alles ausfüllen.")
            return
        self.login_msg.config(text="Anmelden...", fg=ModernStyle.TEXT_MUTED)
        threading.Thread(target=lambda: self._perform_login(u, p), daemon=True).start()

    def _perform_login(self, user, pw):
        try:
            data = self.api_call('auth.php?action=login', 'POST', {'email': user, 'password': pw})
            if data.get('success'):
                self.api_key = data['api_key']
                self.config['api_key'] = self.api_key
                save_config(self.config)
                self.root.after(0, self._on_login_success)
            else:
                self.root.after(0, lambda: self.login_msg.config(text=data.get('error', 'Login fehlgeschlagen'), fg=ModernStyle.DANGER))
        except Exception as e:
            self.root.after(0, lambda err=str(e): self.login_msg.config(text=f"Netzwerkfehler: {err[:80]}", fg=ModernStyle.DANGER))

    def _on_login_success(self):
        data = self.api_call('billing.php?action=status')
        if not data.get('success'):
            self.api_key = None
            self.show_login()
            self.login_msg.config(text="Sitzung ungültig.", fg=ModernStyle.DANGER)
            return
        self.user_info = data
        self.premium_data = self.api_call('premium.php?action=status') or {}
        self.log(f"Hallo {self.user_info.get('username','')} | {self.user_info.get('role','user')}", 'info')

        self.premium_badge.config(text='PREMIUM' if self.is_premium() else 'FREE',
                                  fg=ModernStyle.GOLD if self.is_premium() else ModernStyle.TEXT_MUTED)
        is_admin = bool(self.user_info.get('is_admin')) or self.user_info.get('role') == 'superadmin'
        if is_admin:
            self.admin_btn.pack(side='left', padx=(0,8))
            self.log('Admin-Modus aktiv.', 'success')
        else:
            self.admin_btn.pack_forget()

        self.connection_badge.config(text='● ONLINE', fg=ModernStyle.SUCCESS)
        self.show_dashboard()

    # ---------- DASHBOARD ----------
    def show_dashboard(self):
        self.clear_content()
        top = tk.Frame(self.content, bg=ModernStyle.BG)
        top.pack(fill='x', pady=(0,18))

        section_title(top, "Spiel auswählen", "🎮").pack(side='left', padx=(0,12))
        combo_wrap = tk.Frame(top, bg=ModernStyle.BORDER, highlightbackground=ModernStyle.ACCENT, highlightthickness=1, bd=0)
        combo_wrap.pack(side='left', fill='y')
        self.game_combo = ModernCombobox(combo_wrap, values=[], width=42)
        self.game_combo.pack(side='left', fill='y', padx=1, pady=1)
        self.game_combo.bind('<<ComboboxSelected>>', self.on_game_selected)
        self.load_games()

        secondary_btn(top, "Prozess prüfen", self.check_process, "🔍").pack(side='left', padx=(14,0))
        self.proc_status = tk.Label(top, text="Kein Spiel gestartet", bg=ModernStyle.BG,
                                    fg=ModernStyle.DANGER, font=ModernStyle.FONT_BODY)
        self.proc_status.pack(side='left', padx=(14,0))

        main = tk.Frame(self.content, bg=ModernStyle.BG)
        main.pack(fill='both', expand=True)

        left = tk.Frame(main, bg=ModernStyle.BG)
        left.pack(side='left', fill='both', expand=True)

        # Stats
        stats = tk.Frame(left, bg=ModernStyle.BG)
        stats.pack(fill='x', pady=(0,16))
        self._create_stat_card(stats, "Spiele", "0", 0)
        self._create_stat_card(stats, "Trainer", "0", 1)
        self._create_stat_card(stats, "Favoriten", str(len(self.favorites)), 2)
        self._create_stat_card(stats, "Premium", "AKTIV" if self.is_premium() else "FREE", 3)

        # Recent games
        recents = RoundedFrame(left, radius=16, bg=ModernStyle.BG_CARD, border_color=ModernStyle.BORDER)
        recents.pack(fill='x', pady=(0,16))
        recents.inner.configure(padx=20, pady=18)
        section_title(recents.inner, "Zuletzt verwendet", "🕐").pack(anchor='w')
        self.recent_container = tk.Frame(recents.inner, bg=ModernStyle.BG_CARD)
        self.recent_container.pack(fill='x', pady=(12,0))
        self._render_recent_games()

        # Trainers
        tcard = RoundedFrame(left, radius=16, bg=ModernStyle.BG_CARD, border_color=ModernStyle.BORDER)
        tcard.pack(fill='both', expand=True)
        tcard.inner.configure(padx=20, pady=18)
        h = tk.Frame(tcard.inner, bg=ModernStyle.BG_CARD)
        h.pack(fill='x', pady=(0,10))
        section_title(h, "Verfügbare Trainer", "🛠").pack(side='left')
        tk.Label(h, text="Filter:", bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_BODY).pack(side='right')
        self.trainer_filter = tk.Entry(h, bg=ModernStyle.BG_INPUT, fg=ModernStyle.TEXT,
                                       insertbackground=ModernStyle.ACCENT, relief='flat',
                                       font=ModernStyle.FONT_BODY, width=24,
                                       highlightbackground=ModernStyle.BORDER, highlightthickness=1)
        self.trainer_filter.pack(side='right', ipady=4, padx=(10,0))
        self.trainer_filter.bind('<KeyRelease>', lambda e: self.render_trainers())

        # Trainer grid (WeMod-style toggles)
        self.trainer_scroll = ttk.Scrollbar(tcard.inner, orient='vertical')
        self.trainer_container = tk.Canvas(tcard.inner, bg=ModernStyle.BG_CARD, highlightthickness=0,
                                           yscrollcommand=self.trainer_scroll.set)
        self.trainer_scroll.config(command=self.trainer_container.yview)
        self.trainer_container.pack(side='left', fill='both', expand=True)
        self.trainer_scroll.pack(side='right', fill='y')
        self.trainer_toggle_buttons = {}

        # Right sidebar
        right = tk.Frame(main, bg=ModernStyle.BG, width=360)
        right.pack(side='right', fill='y', padx=(20,0))
        right.pack_propagate(False)

        log_card = RoundedFrame(right, radius=16, bg=ModernStyle.BG_CARD, border_color=ModernStyle.BORDER)
        log_card.pack(fill='both', expand=True)
        log_card.inner.configure(padx=16, pady=16)
        section_title(log_card.inner, "Live-Log", "📜").pack(anchor='w')
        self.log_box = scrolledtext.ScrolledText(log_card.inner, bg=ModernStyle.BG_INPUT, fg=ModernStyle.TEXT,
                                                 font=('JetBrains Mono', 9), wrap='word', state='disabled',
                                                 relief='flat', highlightthickness=1, highlightbackground=ModernStyle.BORDER)
        self.log_box.pack(fill='both', expand=True, pady=(12,0))

        tools = RoundedFrame(right, radius=16, bg=ModernStyle.BG_CARD, border_color=ModernStyle.BORDER)
        tools.pack(fill='x', pady=(16,0))
        tools.inner.configure(padx=16, pady=16)
        section_title(tools.inner, "Tools", "🧰").pack(anchor='w')
        bf = tk.Frame(tools.inner, bg=ModernStyle.BG_CARD)
        bf.pack(fill='x', pady=(12,0))
        ghost_btn(bf, "Changelog", self.show_changelog).pack(side='left', padx=(0,8))
        ghost_btn(bf, "Verlauf", self.show_history).pack(side='left', padx=(0,8))
        ghost_btn(bf, "Log", self.export_log).pack(side='left')
        tk.Label(tools.inner, text="F5 = Prozess prüfen | F1 = Hilfe",
                 bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_SMALL).pack(anchor='w', pady=(12,0))

        self.root.bind('<F5>', lambda e: self.check_process())
        self.root.bind('<F1>', lambda e: self.show_tutorial())

        self.load_trainers()

    def _create_stat_card(self, parent, label, value, idx):
        f = stat_card(parent, label, value, "●")
        f.pack(side='left', fill='x', expand=True, padx=(0 if idx==0 else 10,0))
        for w in f.winfo_children():
            if isinstance(w, tk.Label) and w.cget('font')[0] == 'Rajdhani':
                if label == 'Premium':
                    w.config(fg=ModernStyle.GOLD if value == "AKTIV" else ModernStyle.TEXT_MUTED)
                if label == 'Spiele':
                    self.games_count_label = w
                elif label == 'Trainer':
                    self.trainers_count_label = w

    def _render_recent_games(self):
        for w in self.recent_container.winfo_children(): w.destroy()
        if not self.recent_games:
            tk.Label(self.recent_container, text="Noch keine Spiele verwendet.", bg=ModernStyle.BG_CARD,
                     fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_BODY).pack(anchor='w')
            return
        for slug in self.recent_games[:5]:
            game = next((g for g in self.games if g.get('slug') == slug), None)
            if not game: continue
            secondary_btn(self.recent_container, game.get('name', slug), lambda s=slug: self.select_game(s)).pack(side='left', padx=(0,8))

    def open_live_scan(self):
        if not self.current_game:
            self.log('Wähle zuerst ein Spiel aus!', 'error'); return
        self._open_dialog('Live Memory Scan', 520, 420, lambda inner: self._render_live_scan(inner))

    def _render_live_scan(self, inner):
        section_title(inner, 'Live Scan (WeMod Style)', '🔍').pack(anchor='w', pady=(0,14))
        tk.Label(inner, text='1. Trage den aktuellen Wert ein (z.B. Geld, HP).', bg=ModernStyle.BG_CARD,
                 fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_BODY, justify='left').pack(anchor='w')
        tk.Label(inner, text='2. Ändere den Wert im Spiel und suche erneut.', bg=ModernStyle.BG_CARD,
                 fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_BODY, justify='left').pack(anchor='w')
        tk.Label(inner, text='3. Sobald nur noch 1-5 Adressen übrig sind, patchen.', bg=ModernStyle.BG_CARD,
                 fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_BODY, justify='left').pack(anchor='w')

        row = tk.Frame(inner, bg=ModernStyle.BG_CARD)
        row.pack(fill='x', pady=12)
        tk.Label(row, text='Wert:', bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT, font=ModernStyle.FONT_BODY).pack(side='left')
        self.scan_value_entry = tk.Entry(row, bg=ModernStyle.BG_INPUT, fg=ModernStyle.TEXT, insertbackground=ModernStyle.ACCENT,
                                         relief='flat', highlightbackground=ModernStyle.BORDER, highlightthickness=1, font=ModernStyle.FONT_BODY)
        self.scan_value_entry.pack(side='left', fill='x', expand=True, padx=(8,8))
        self.scan_type_var = tk.StringVar(value='int32')
        type_combo = ttk.Combobox(row, textvariable=self.scan_type_var, values=['int32','int64','float','double'], width=10, state='readonly')
        type_combo.pack(side='left')

        self.scan_addresses = []
        self.scan_log = scrolledtext.ScrolledText(inner, height=8, bg=ModernStyle.BG_DARK, fg=ModernStyle.TEXT,
                                                 font=ModernStyle.FONT_SMALL, relief='flat', state='disabled')
        self.scan_log.pack(fill='both', expand=True, pady=8)

        def log(msg):
            self.scan_log.configure(state='normal')
            self.scan_log.insert(tk.END, msg + '\n')
            self.scan_log.see(tk.END)
            self.scan_log.configure(state='disabled')

        def do_scan():
            if not self.current_game_pid:
                log('Fehler: Spiel-Prozess nicht erkannt. Bitte zuerst Prozess prüfen.'); return
            val = self.scan_value_entry.get().strip()
            if not val: log('Bitte Wert eingeben.'); return
            try:
                if self.scan_type_var.get() in ('float','double'): val = float(val)
                else: val = int(val)
            except ValueError:
                log('Ungültiger Wert.'); return
            log(f'Scanne nach {val} ({self.scan_type_var.get()}) ...')
            def worker():
                from activation_engine import ActivationEngine
                engine = ActivationEngine()
                ok, msg = engine.attach(self.current_game_pid)
                if not ok:
                    log(f'Attach fehlgeschlagen: {msg}'); return
                if not self.scan_addresses:
                    hits = engine.scan(val, self.scan_type_var.get())
                else:
                    hits = engine.rescan(self.scan_addresses, val, self.scan_type_var.get())
                self.scan_addresses = hits
                log(f'{len(hits)} Adressen gefunden')
                for a in hits[:10]:
                    log(f'  {hex(a)}')
            threading.Thread(target=worker, daemon=True).start()

        def do_patch():
            if len(self.scan_addresses) > 10:
                log('Zu viele Adressen — bitte weiter eingrenzen.'); return
            val = simpledialog.askstring('Neuer Wert', 'Neuen Wert eingeben:', parent=self.root)
            if val is None: return
            try:
                if self.scan_type_var.get() in ('float','double'): val = float(val)
                else: val = int(val)
            except ValueError:
                log('Ungültiger Wert.'); return
            def worker():
                from activation_engine import ActivationEngine
                engine = ActivationEngine()
                ok, msg = engine.attach(self.current_game_pid)
                if not ok: log(msg); return
                for addr in self.scan_addresses:
                    engine.patch(addr, val, self.scan_type_var.get())
                log(f'Gepatcht auf {val} bei {len(self.scan_addresses)} Adressen.')
            threading.Thread(target=worker, daemon=True).start()

        def do_freeze():
            if len(self.scan_addresses) > 10:
                log('Zu viele Adressen zum freezen.'); return
            val = self.scan_value_entry.get().strip()
            try:
                if self.scan_type_var.get() in ('float','double'): val = float(val)
                else: val = int(val)
            except ValueError: log('Ungültiger Wert.'); return
            def worker():
                from activation_engine import ActivationEngine
                engine = ActivationEngine()
                ok, msg = engine.attach(self.current_game_pid)
                if not ok: log(msg); return
                for addr in self.scan_addresses:
                    engine.freeze(addr, val, self.scan_type_var.get())
                log(f'Freeze aktiv auf {len(self.scan_addresses)} Adressen.')
            threading.Thread(target=worker, daemon=True).start()

        bf = tk.Frame(inner, bg=ModernStyle.BG_CARD)
        bf.pack(fill='x', pady=(8,0))
        primary_btn(bf, 'Scan', do_scan).pack(side='left', padx=(0,8))
        secondary_btn(bf, 'Patch', do_patch).pack(side='left', padx=(0,8))
        secondary_btn(bf, 'Freeze', do_freeze).pack(side='left', padx=(0,8))
        AnimatedButton(bf, text='Schließen', command=lambda: inner.master.master.destroy(), width=110, height=34).pack(side='right')

    def render_trainers(self):
        self.trainer_container.delete('all')
        self.trainer_toggle_buttons.clear()
        ftxt = self.trainer_filter.get().lower()

        items = []
        # Cloud patterns from API (one-click toggles)
        if self.trainers:
            base_patterns = self.trainers[0].get('cloud_patterns', [])
            for p in base_patterns:
                if ftxt and ftxt not in p['cheat_name'].lower(): continue
                items.append({'type': 'cloud', 'name': p['cheat_name'], 'key': p['cheat_key'], 'data': p})

        # Legacy trainers: live_scan, bridge, savegame, memory
        for trainer in self.trainers:
            if ftxt and ftxt not in trainer['name'].lower(): continue
            items.append({'type': 'legacy', 'name': trainer['name'], 'key': f"trainer_{trainer.get('trainer_id')}", 'data': trainer})

        if not items:
            self.trainer_container.create_text(20, 20, anchor='nw', text='Keine Trainer verfügbar.',
                                               fill=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_BODY)
            return

        cols = 2
        w = 260
        h = 90
        pad = 12
        for i, item in enumerate(items):
            row = i // cols
            col = i % cols
            x = col * (w + pad)
            y = row * (h + pad)
            self._draw_toggle_card(x, y, w, h, item)

        bbox = self.trainer_container.bbox('all')
        if bbox:
            self.trainer_container.configure(scrollregion=bbox)

    def _draw_toggle_card(self, x, y, w, h, item):
        key = item['key']
        data = item['data']
        is_live_scan = item['type'] == 'legacy' and data.get('method') == 'live_scan'
        is_one_click = item['type'] == 'cloud' or (item['type'] == 'legacy' and data.get('method') in ('bridge', 'savegame', 'memory', 'toggle'))
        enabled = self.active_toggles.get(key, False) if is_one_click else False
        bg = ModernStyle.SUCCESS if enabled else ModernStyle.BG_ELEVATED
        fg = ModernStyle.BG if enabled else ModernStyle.TEXT
        tag = f'toggle_{key}'

        rect = self.trainer_container.create_rectangle(x, y, x+w, y+h, fill=bg, outline=ModernStyle.BORDER, width=1, tags=(tag,))
        name = item['name']
        if is_live_scan:
            name = f"🔍 {name}"
        text = self.trainer_container.create_text(x+15, y+20, anchor='nw', text=name,
                                                    fill=fg, font=ModernStyle.FONT_BODY_B, width=w-30, tags=(tag,))
        status_text = 'AN' if enabled else ('SCAN' if is_live_scan else 'AUS')
        status = self.trainer_container.create_text(x+w-15, y+h-15, anchor='se', text=status_text,
                                                   fill=fg, font=ModernStyle.FONT_SMALL, tags=(tag,))

        def click(e, it=item, k=key):
            self._toggle_cheat(it, k)

        for t in (rect, text, status):
            self.trainer_container.tag_bind(t, '<Button-1>', click)
            self.trainer_container.tag_bind(t, '<Enter>', lambda e, t=tag: self.trainer_container.itemconfig(t, outline=ModernStyle.ACCENT))
            self.trainer_container.tag_bind(t, '<Leave>', lambda e, t=tag, en=enabled: self.trainer_container.itemconfig(t, outline=ModernStyle.SUCCESS if en else ModernStyle.BORDER))
        self.trainer_toggle_buttons[key] = {'rect': rect, 'text': text, 'status': status}

    def _toggle_cheat(self, item, key):
        data = item['data']
        if item['type'] == 'legacy' and data.get('method') == 'live_scan':
            self.open_live_scan()
            return
        if not hasattr(self, 'active_toggles'):
            self.active_toggles = {}
        enabled = not self.active_toggles.get(key, False)
        self.log(f"{'Aktiviere' if enabled else 'Deaktiviere'} {item['name']}...", 'info')

        def worker():
            from activation_engine import ActivationEngine
            engine = ActivationEngine()
            if item['type'] == 'cloud':
                data['method'] = 'toggle'
                success, msg = engine.toggle(self.current_game, data)
            else:
                success, msg = engine.activate(self.current_game, data)
            self.root.after(0, lambda: self.log(msg, 'success' if success else 'error'))
            if success:
                self.active_toggles[key] = enabled
                self.root.after(0, self.render_trainers)
        threading.Thread(target=worker, daemon=True).start()

    def on_trainer_select(self, event=None):
        pass

    # ---------- API / STATE ----------
    def api_call(self, endpoint, method='GET', data=None):
        headers = {'User-Agent': f'SweetCheat-Desktop/{APP_VERSION}', 'Accept': 'application/json'}
        if self.api_key:
            headers['Authorization'] = f'Bearer {self.api_key}'
        url = f'{self.api_base}/{endpoint}'
        try:
            if method == 'GET':
                req = urllib.request.Request(url, headers=headers)
                with urllib.request.urlopen(req, timeout=15) as r:
                    return json.loads(r.read().decode('utf-8'))
            body = json.dumps(data).encode('utf-8') if data else b''
            req = urllib.request.Request(url, data=body,
                                         headers={**headers, 'Content-Type': 'application/json'}, method='POST')
            with urllib.request.urlopen(req, timeout=15) as r:
                return json.loads(r.read().decode('utf-8'))
        except urllib.error.HTTPError as e:
            try:
                return json.loads(e.read().decode('utf-8'))
            except Exception:
                return {'success': False, 'error': f'HTTP {e.code}'}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def is_premium(self):
        return bool(self.premium_data.get('premium', False))


    def auto_select_first_game(self):
        if self.current_game: return
        if self.games:
            first = self.games[0]
            self.game_combo.set(first['name'])
            self.select_game(first['slug'])
            self.log(f'Automatisch ausgewählt: {first["name"]}', 'info')

    def load_games(self):
        self.log('Lade Spieleliste...', 'info')
        games_to_load = []
        data = self.api_call('games.php?action=list')
        if data and data.get('success'):
            games_to_load = data.get('games', [])
            self.log(f'{len(games_to_load)} Spiele via API geladen.', 'success')
        if not games_to_load:
            for fname in ['games_backup.json', 'games.json', 'data/games.json']:
                try:
                    with open(fname, 'r', encoding='utf-8') as f:
                        games_to_load = json.load(f)
                    self.log(f'Backup geladen: {fname}', 'info')
                    break
                except Exception: pass
        if not games_to_load:
            games_to_load = [
                {"name": "Stardew Valley", "slug": "stardew-valley"},
                {"name": "Minecraft: Java Edition", "slug": "minecraft-java"},
                {"name": "Factorio", "slug": "factorio"},
                {"name": "The Witcher 3", "slug": "witcher-3"},
                {"name": "Cyberpunk 2077", "slug": "cyberpunk-2077"},
                {"name": "Grand Theft Auto V", "slug": "gta-v"}
            ]
            self.log('Hardcoded-Fallback aktiv.', 'warn')
        self.games = []
        for g in games_to_load:
            if isinstance(g, dict) and g.get('name'):
                self.games.append({
                    'name': str(g['name']),
                    'slug': str(g.get('slug', g['name'])).lower().replace(' ', '-'),
                    'exe': str(g.get('process_name', g.get('exe', ''))).lower().strip(),
                    'launcher_processes': str(g.get('launcher_processes', '')).lower().strip(),
                    'platform': str(g.get('platform', 'Other')).strip()
                })
        if hasattr(self, 'game_combo'):
            names = [g['name'] for g in self.games]
            self.game_combo.config(values=names)
            self.game_combo.set(names[0] if names else "Keine Spiele")
            self.game_combo.update_idletasks()
            self.game_combo.event_generate('<<ComboboxSelected>>')
            self.log(f'UI aktualisiert: {len(names)} Spiele.', 'info')
            self.root.after(200, self.auto_select_first_game)
        if hasattr(self, 'games_count_label'):
            self.games_count_label.config(text=str(len(self.games)))

    def on_game_selected(self, event=None):
        name = self.game_combo.get()
        if not name or name == 'Keine Spiele': return
        game = next((g for g in self.games if g['name'] == name), None)
        if game: self.select_game(game['slug'])

    def select_game(self, slug):
        self.current_game = next((g for g in self.games if g['slug'] == slug), None)
        if not self.current_game: return
        self.log(f"Spiel ausgewählt: {self.current_game['name']}", 'info')
        self.load_trainers()
        self.check_process()

    def load_trainers(self):
        if not self.current_game:
            self.log('load_trainers: kein Spiel ausgewählt', 'warn')
            return
        self.log(f"Lade Trainer für {self.current_game['name']}...", 'info')
        endpoint = f'trainers.php?game={self.current_game["slug"]}'
        self.log(f'API: {endpoint}', 'info')
        data = self.api_call(endpoint)
        self.log(f'Trainer response: {data.get("success", False)} - {data.get("error", "ok")}', 'info')
        if data.get('success'):
            self.trainers = data.get('trainers', [])
            count = len(self.trainers)
            self.log(f'{count} Trainer geladen.', 'success')
            def _update():
                if hasattr(self, 'trainers_count_label') and self.trainers_count_label:
                    try:
                        self.trainers_count_label.config(text=str(count))
                        self.games_count_label.config(text=str(len(self.games)))
                    except Exception as e:
                        self.log(f'Label update error: {e}', 'error')
                self.render_trainers()
            self.root.after(0, _update)
        else:
            self.log(f'Fehler beim Laden der Trainer: {data.get("error", "Unbekannt")}', 'error')

    def activate_trainer(self, trainer):
        if not self.current_game:
            self.log('Kein Spiel ausgewählt!', 'error'); return
        self.log(f"Aktiviere {trainer['name']}...", 'info')
        def worker():
            from activation_engine import ActivationEngine
            engine = ActivationEngine()
            success, msg = engine.activate(self.current_game, trainer)
            self.root.after(0, lambda: self.log(msg, 'success' if success else 'error'))
            self.log_activation(trainer['name'], success=success)
        threading.Thread(target=worker, daemon=True).start()

    def check_process(self):
        if not self.current_game:
            self.log('Wähle zuerst ein Spiel aus!', 'error'); return
        
        slug = self.current_game.get('slug', '')
        # Gather process names from API fields
        extra = []
        for key in ['exe', 'process_name']:
            v = self.current_game.get(key, '').strip()
            if v: extra.append(v)
        launcher = self.current_game.get('launcher_processes', '')
        if launcher:
            for p in launcher.split(','):
                p = p.strip()
                if p: extra.append(p)
        
        self.log(f"Suche laufenden Prozess für {slug}...", 'info')
        
        def worker():
            if not WINDOWS:
                self.root.after(0, lambda: self.log('Prozessprüfung nur unter Windows verfügbar.', 'error')); return
            
            try:
                scanner = ProcessScanner()
                hit = scanner.scan_for_game(slug, extra)
                
                if hit:
                    self.current_game_pid = hit['pid']
                    self.root.after(0, lambda h=hit: self.proc_status.config(text=f"Online: {h['name']} (PID {h['pid']})", fg=ModernStyle.SUCCESS))
                    self.log(f"Prozess gefunden! {hit['name']} (PID {hit['pid']})", 'success')
                    return
                
                # Fallback: direct Pymem attempts
                if pymem:
                    for cand in extra:
                        try:
                            pm = Pymem.Pymem(cand)
                            pid = pm.process_id
                            self.current_game_pid = pid
                            self.root.after(0, lambda c=cand, p=pid: self.proc_status.config(text=f"Online: {c} (PID {p})", fg=ModernStyle.SUCCESS))
                            self.log(f"Prozess via Pymem gefunden: {cand} (PID {pid})", 'success')
                            pm.close_process()
                            return
                        except Exception: pass
                
                self.current_game_pid = None
                self.root.after(0, lambda: self.proc_status.config(text="Nicht gestartet", fg=ModernStyle.DANGER))
                self.log(f"Prozess nicht gefunden. Slug: {slug} | Extra: {', '.join(extra[:4]) or '-'}", 'warning')
            except Exception as e:
                self.log(f"Fehler bei Prozessprüfung: {e}", 'error')
        
        threading.Thread(target=worker, daemon=True).start()

    # ---------- LOGGING ----------
    def log(self, msg, level='info'):
        t = time.strftime('%H:%M:%S')
        prefix = {'info': 'INFO', 'success': 'OK', 'warning': 'WARN', 'error': 'ERR'}
        formatted = f"[{t}] [{prefix.get(level, 'INFO')}] {msg}"
        self.log_lines.append(formatted)
        self.root.after(0, self._update_log_ui)

    def _update_log_ui(self):
        self.log_box.configure(state='normal')
        self.log_box.insert(tk.END, self.log_lines[-1] + '\n')
        self.log_box.see(tk.END)
        self.log_box.configure(state='disabled')

    def export_log(self):
        try:
            with open("sweetcheat_log.txt", 'w', encoding='utf-8') as f:
                f.write('\n'.join(self.log_lines))
            messagebox.showinfo("Erfolg", "Log gespeichert unter sweetcheat_log.txt")
        except Exception as e:
            messagebox.showerror("Fehler", str(e))

    def log_activation(self, name, success=True):
        try:
            self.api_call('trainer-logs.php?action=log', 'POST',
                          {'trainer_name': name, 'game_slug': self.current_game.get('slug') if self.current_game else '',
                           'success': success, 'client_version': APP_VERSION})
        except Exception:
            pass

    # ---------- WINDOWS ----------
    def show_changelog(self, first_start=False):
        data = self.api_call('changelog.php')
        if not data.get('success'):
            if not first_start:
                messagebox.showerror("Fehler", "Changelog konnte nicht geladen werden.")
            return
        versions = data.get('versions', [])
        if first_start and versions and self.config.get('last_seen_changelog') == versions[0].get('version'):
            return
        self._open_dialog("Was ist neu?", 520, 600, lambda inner: self._render_changelog(inner, versions))
        if versions:
            self.config['last_seen_changelog'] = versions[0].get('version')
            save_config(self.config)

    def _render_changelog(self, inner, versions):
        section_title(inner, "SweetCheat Updates", "🚀").pack(anchor='w', pady=(0,14))
        for v in versions[:5]:
            vc = card(inner, padx=18, pady=14)
            vc.pack(fill='x', pady=(0,10))
            tk.Label(vc, text=f"v{v.get('version')} — {v.get('date')}", bg=ModernStyle.BG_CARD,
                     fg=ModernStyle.ACCENT, font=ModernStyle.FONT_BODY_B).pack(anchor='w')
            tk.Label(vc, text=v.get('title', ''), bg=ModernStyle.BG_CARD,
                     fg=ModernStyle.TEXT, font=ModernStyle.FONT_BODY_B).pack(anchor='w', pady=(4,0))
            for c in v.get('changes', []):
                tk.Label(vc, text=f"• {c}", bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT_MUTED,
                         font=ModernStyle.FONT_SMALL, wraplength=420, justify='left').pack(anchor='w', pady=(1,0))
        AnimatedButton(inner, text="Schließen", command=lambda: inner.master.master.destroy(), width=120, height=36).pack(anchor='e', pady=(16,0))

    def show_history(self):
        data = self.api_call('trainer-logs.php?action=list&limit=100')
        stats = self.api_call('trainer-logs.php?action=stats')
        self._open_dialog("Trainer-Verlauf", 660, 540, lambda inner: self._render_history(inner, data, stats))

    def _render_history(self, inner, data, stats):
        section_title(inner, "Aktivierungen/Tag", "📊").pack(anchor='w')
        if data.get('success') and data.get('logs'):
            days = Counter()
            for l in data['logs']:
                day = time.strftime('%d.%m', time.localtime(l.get('created_at', 0)))
                days[day] += 1
            labels, values = list(days.keys())[:14][::-1], [days.get(d, 0) for d in list(days.keys())[:14][::-1]]
            max_v = max(values) if values else 1
            bf = tk.Frame(inner, bg=ModernStyle.BG_CARD)
            bf.pack(fill='x', pady=(12,0))
            for d, v in zip(labels, values):
                col = tk.Frame(bf, bg=ModernStyle.BG_CARD)
                col.pack(side='left', expand=True)
                h = max(20, int((v / max_v) * 80))
                tk.Frame(col, bg=ModernStyle.ACCENT, width=26, height=h).pack()
                tk.Label(col, text=str(v), bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT, font=ModernStyle.FONT_SMALL).pack()
                tk.Label(col, text=d, bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_SMALL).pack()
        if stats.get('success'):
            sf = tk.Frame(inner, bg=ModernStyle.BG_CARD)
            sf.pack(fill='x', pady=(20,0))
            for lbl, val, col in [
                ('Gesamt', stats.get('total_activations', 0), ModernStyle.TEXT),
                ('Erfolgreich', stats.get('successful', 0), ModernStyle.SUCCESS),
                ('Fehler', stats.get('failed', 0), ModernStyle.DANGER)
            ]:
                c = tk.Frame(sf, bg=ModernStyle.BG_CARD)
                c.pack(side='left', expand=True)
                tk.Label(c, text=str(val), bg=ModernStyle.BG_CARD, fg=col, font=('Rajdhani', 26, 'bold')).pack()
                tk.Label(c, text=lbl, bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_SMALL).pack()
        lb = scrolledtext.ScrolledText(inner, bg=ModernStyle.BG_INPUT, fg=ModernStyle.TEXT,
                                       font=('JetBrains Mono', 9), height=10, relief='flat',
                                       highlightthickness=1, highlightbackground=ModernStyle.BORDER)
        lb.pack(fill='both', expand=True, pady=(16,0))
        for l in data.get('logs', [])[:50]:
            t = time.strftime('%d.%m.%Y %H:%M', time.localtime(l.get('created_at', 0)))
            s = '✅' if l.get('success') else '❌'
            lb.insert(tk.END, f"{t} {s} {l.get('action','?')} ({l.get('game_name','?')} / {l.get('trainer_name','?')})\n")

    def show_account(self):
        self._open_dialog("Account", 440, 360, lambda inner: self._render_account(inner))

    def _render_account(self, inner):
        u = self.user_info or {}
        rows = [
            ("Benutzer", u.get('username', '-')),
            ("E-Mail", u.get('email', '-')),
            ("Rolle", u.get('role', 'user').upper()),
            ("Status", 'PREMIUM' if self.is_premium() else 'FREE'),
            ("Ablauf", u.get('subscription_end', '-')[:10] if u.get('subscription_end') else '-'),
        ]
        for label, val in rows:
            f = tk.Frame(inner, bg=ModernStyle.BG_CARD)
            f.pack(fill='x', pady=6)
            tk.Label(f, text=label, bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_BODY).pack(side='left')
            tk.Label(f, text=val, bg=ModernStyle.BG_CARD, fg=ModernStyle.ACCENT if label == 'Status' and val == 'PREMIUM' else ModernStyle.TEXT,
                     font=ModernStyle.FONT_BODY_B).pack(side='right')
        AnimatedButton(inner, text="Zurück", command=lambda: inner.master.master.destroy(), width=120, height=38).pack(anchor='e', pady=(20,0))

    def show_settings(self):
        self.clear_content()
        win = tk.Frame(self.content, bg=ModernStyle.BG)
        win.pack(fill='both', expand=True)
        card = RoundedFrame(win, radius=16, bg=ModernStyle.BG_CARD, border_color=ModernStyle.BORDER)
        card.pack(fill='both', expand=True, padx=20, pady=20)
        inner = card.inner
        inner.configure(padx=30, pady=30)
        section_title(inner, "Einstellungen", "⚙").pack(anchor='w')
        tk.Label(inner, text="Hier erscheinen bald Theme- und Hotkey-Einstellungen.",
                 bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_BODY, wraplength=500).pack(anchor='w', pady=(20,0))
        ghost_btn(inner, "Zurück", self.show_dashboard).pack(anchor='w', pady=(30,0))

    def show_admin_panel(self):
        self._open_dialog("🛡️ Admin Panel", 520, 420, lambda inner: (
            section_title(inner, "Administration", "🛡️").pack(anchor='w'),
            tk.Label(inner, text="Hier erscheinen Benutzer-, Lizenz- und Inhaltsverwaltung.",
                     bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_BODY, wraplength=440).pack(anchor='w', pady=(14,0))
        ))

    def show_tutorial(self):
        self._open_dialog("Hilfe", 500, 400, lambda inner: (
            section_title(inner, "Schnellstart", "❓").pack(anchor='w', pady=(0,14)),
            tk.Label(inner, text="1. Wähle ein Spiel aus dem Dropdown.\n2. Starte das Spiel.\n3. Klicke auf \"Prozess prüfen\".\n4. Aktiviere den gewünschten Trainer.",
                     bg=ModernStyle.BG_CARD, fg=ModernStyle.TEXT_MUTED, font=ModernStyle.FONT_BODY, justify='left', wraplength=440).pack(anchor='w')
        ))

    def _open_dialog(self, title, w, h, builder):
        win = tk.Toplevel(self.root)
        win.title(title)
        win.geometry(f"{w}x{h}")
        win.configure(bg=ModernStyle.BG)
        win.transient(self.root)
        c = RoundedFrame(win, radius=16, bg=ModernStyle.BG_CARD, border_color=ModernStyle.BORDER)
        c.pack(fill='both', expand=True, padx=20, pady=20)
        c.inner.configure(padx=24, pady=24)
        section_title(c.inner, title, "").pack(anchor='w', pady=(0,10))
        builder(c.inner)

    def logout(self):
        self.api_key = None
        self.config['api_key'] = None
        save_config(self.config)
        self.premium_badge.config(text='FREE', fg=ModernStyle.TEXT_MUTED)
        self.admin_btn.pack_forget()
        self.connection_badge.config(text='● OFFLINE', fg=ModernStyle.DANGER)
        self.show_login()

    # ---------- BACKGROUND / MISC ----------
    def validate_key(self):
        data = self.api_call('auth.php?action=validate')
        if data.get('success'):
            self._on_login_success()
        else:
            self.show_login()

    def start_background_tasks(self):
        if BRIDGE:
            threading.Thread(target=self._smapi_poll, daemon=True).start()
        self.check_for_update()
        # Auto-watch game process and auto-attach for active toggles
        def watch_loop():
            while True:
                time.sleep(3)
                if self.current_game:
                    try:
                        self.check_process()
                    except Exception as e:
                        logger.error(f"background watch error: {e}")
        threading.Thread(target=watch_loop, daemon=True).start()

    def _smapi_poll(self):
        last = {}
        while True:
            time.sleep(5)
            if self.current_game and self.current_game.get('slug') == 'stardew-valley':
                try:
                    if not self.bridge_client:
                        client = stardew_bridge.StardewBridgeClient()
                        if client.connect(timeout=1.0):
                            self.bridge_client = client
                            self.log("SMAPI Bridge verbunden.", 'success')
                    if self.bridge_client:
                        vals = {stat: self.bridge_client.get(stat) for stat in ['money', 'health', 'stamina']}
                        if vals != last:
                            last = vals
                            self.log(f"Live | ${vals.get('money','?')} | HP {vals.get('health','?')} | EN {vals.get('stamina','?')}", 'info')
                except Exception: pass

    def check_for_update(self):
        if sys.platform != 'win32': return
        try:
            import updater
            self.log('Prüfe auf Updates...', 'info')
            def cb(success, msg):
                if not success: self.log(f"Update-Check: {msg}", 'info')
            updater.check_and_install_update(parent_app=self, finished_callback=cb)
        except Exception as e:
            self.log(f"Update-Modul fehlt: {e}", 'warning')

    def open_savegame_editor(self, trainer):
        messagebox.showinfo("Savegame Editor", "Savegame Editor wird geladen...")

    def open_pattern_learner(self, trainer):
        messagebox.showinfo("Pattern Learner", "Pattern Learner wird geladen...")

    def init_hotkeys(self):
        pass

    def apply_theme(self, theme):
        pass

    def run(self):
        self.root.mainloop()

if __name__ == '__main__':
    SweetCheatGUI().run()
