"""Einstellungen: gruppierte Karten statt Formular-Flickenteppich.

Hardware (Bildschirm, Eingabe-Methode, Game Window), Hotkeys,
AutoHur, Alarm und Clear-Stats — gleiche Context-Aufrufe wie vorher,
aber mit Hotkey-Erfassung, Such-Auswahl und Inline-Status.
"""
import re

import customtkinter
import pygetwindow as gw
import win32gui
from tkinter import filedialog, messagebox
import json

from src.ui import theme
from src.ui.widgets import Card, HotkeyEntry, SearchableComboBox
from src.utils.display import getDisplays, getSelectedDisplay, saveSelectedDisplay
from src.utils.ino import (
    INPUT_METHOD_MAC_BT, INPUT_METHOD_USB, getInputMethod, isArduinoAvailable,
)

INPUT_METHOD_LABELS = {
    'Mac (Bluetooth)': INPUT_METHOD_MAC_BT,
    'Arduino (USB)': INPUT_METHOD_USB,
}
LABEL_BY_METHOD = {method: label for label, method in INPUT_METHOD_LABELS.items()}

HUR_SPELLS = ['utani hur', 'utani gran hur', 'utani tempo hur', 'utamo tempo san']


class ConfigPage(customtkinter.CTkFrame):
    def __init__(self, master, context):
        super().__init__(master, fg_color='transparent')
        self.context = context
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        # Scroll-Container: mit der Design-Karte wird die Seite lang.
        self.scroll = customtkinter.CTkScrollableFrame(
            self, fg_color='transparent')
        self.scroll.grid(row=0, column=0, sticky='nsew')
        self.scroll.grid_columnconfigure((0, 1), weight=1, uniform='cards')

        self._buildHardwareCard()
        self._buildHotkeysCard()
        self._buildAutoHurCard()
        self._buildAlertCard()
        self._buildClearStatsCard()
        self._buildBackupCard()
        self._buildAlliesCard()
        self._buildDesignCard()
        self.refreshHardware()

    # ------------------------------------------------------------------ Karten
    def _buildHardwareCard(self):
        card = Card(self.scroll, 'Hardware', 'Bildschirm, Eingabe und Game Window')
        card.grid(row=1, column=0, columnspan=2, pady=(0, 10), sticky='ew')
        card.body.grid_columnconfigure(1, weight=1)

        customtkinter.CTkLabel(
            card.body, text='Bot-Bildschirm', font=theme.font(12),
            text_color=theme.MUTED,
        ).grid(row=0, column=0, sticky='w', pady=3)
        self.displayCombobox = customtkinter.CTkComboBox(
            card.body, values=['Keine Bildschirme erkannt'],
            state='readonly', command=self.onChangeDisplay,
        )
        self.displayCombobox.grid(row=0, column=1, padx=8, sticky='ew')
        self.arduinoStatusLabel = customtkinter.CTkLabel(
            card.body, text='…', font=theme.font(12),
        )
        self.arduinoStatusLabel.grid(row=0, column=2, padx=8)
        customtkinter.CTkButton(
            card.body, text='Neu erkennen', width=110, height=30,
            corner_radius=8, fg_color=theme.ELEVATED,
            hover_color=theme.BORDER, command=self.refreshHardware,
        ).grid(row=0, column=3)

        customtkinter.CTkLabel(
            card.body, text='Eingabe-Methode', font=theme.font(12),
            text_color=theme.MUTED,
        ).grid(row=1, column=0, sticky='w', pady=3)
        self.inputMethodCombobox = customtkinter.CTkComboBox(
            card.body, values=list(INPUT_METHOD_LABELS.keys()),
            state='readonly', command=self.onChangeInputMethod,
        )
        self.inputMethodCombobox.grid(
            row=1, column=1, columnspan=3, padx=8, sticky='ew')

        customtkinter.CTkLabel(
            card.body, text='Game Window', font=theme.font(12),
            text_color=theme.MUTED,
        ).grid(row=2, column=0, sticky='w', pady=3)
        self.windowsCombobox = customtkinter.CTkComboBox(
            card.body, values=self.getGameWindows(), state='readonly',
            command=self.onChangeWindow,
        )
        self.windowsCombobox.grid(row=2, column=1, padx=8, sticky='ew')
        customtkinter.CTkButton(
            card.body, text='Aktualisieren', width=110, height=30,
            corner_radius=8, fg_color=theme.ELEVATED,
            hover_color=theme.BORDER, command=self.refreshWindows,
        ).grid(row=2, column=3, columnspan=2)

    def _buildHotkeysCard(self):
        card = Card(self.scroll, 'Hotkeys', 'Taste einfach drücken')
        card.grid(row=2, column=0, padx=(0, 5), pady=(0, 10), sticky='nsew')
        hotkeys = self.context.context['general_hotkeys']
        self._hotkeyRow(card.body, 0, 'Shovel',
                        hotkeys['shovel_hotkey'], self.context.setShovelHotkey)
        self._hotkeyRow(card.body, 1, 'Rope',
                        hotkeys['rope_hotkey'], self.context.setRopeHotkey)

    def _hotkeyRow(self, parent, row, label, initial, setter):
        customtkinter.CTkLabel(
            parent, text=label, font=theme.font(12),
            text_color=theme.MUTED,
        ).grid(row=row, column=0, sticky='w', pady=3)
        entry = HotkeyEntry(parent, initial=initial, on_change=setter,
                            width=110)
        entry.grid(row=row, column=1, sticky='e', pady=3)
        return entry

    def _buildAutoHurCard(self):
        card = Card(self.scroll, 'AutoHur', toggled=self.onToggleAutoHur,
                    toggle_initial=self.context.context['auto_hur']['enabled'])
        card.grid(row=2, column=1, padx=(5, 0), pady=(0, 10), sticky='nsew')
        card.body.grid_columnconfigure(1, weight=1)

        customtkinter.CTkLabel(
            card.body, text='Spell', font=theme.font(12),
            text_color=theme.MUTED,
        ).grid(row=0, column=0, sticky='w', pady=3)
        self.hurSpellPicker = SearchableComboBox(
            card.body, values=HUR_SPELLS, placeholder='Spell suchen …',
            command=lambda _v: self.setHurSpell(),
        )
        self.hurSpellPicker.grid(row=0, column=1, sticky='ew', pady=3)
        configSpell = self.context.enabledProfile['config']['auto_hur']['spell']
        if configSpell is not None:
            self.hurSpellPicker.set(configSpell)

        customtkinter.CTkLabel(
            card.body, text='Hotkey', font=theme.font(12),
            text_color=theme.MUTED,
        ).grid(row=1, column=0, sticky='w', pady=3)
        HotkeyEntry(
            card.body, initial=self.context.context['auto_hur']['hotkey'],
            on_change=self.context.setAutoHurHotkey, width=110,
        ).grid(row=1, column=1, sticky='e', pady=3)

        self.pzSwitch = customtkinter.CTkSwitch(
            card.body, text='Auch in PZ nutzen', progress_color=theme.ACCENT,
            command=self.onToggleAutoHurPz,
        )
        self.pzSwitch.grid(row=2, column=0, columnspan=2, sticky='w', pady=3)
        if self.context.context['auto_hur']['pz']:
            self.pzSwitch.select()

    def _buildAlertCard(self):
        card = Card(self.scroll, 'Alarm', toggled=self.onToggleAlert,
                    toggle_initial=self.context.context['alert']['enabled'])
        card.grid(row=3, column=0, padx=(0, 5), pady=(0, 10), sticky='nsew')
        self.alertCaveSwitch = customtkinter.CTkSwitch(
            card.body, text='Nur wenn Cave aktiv',
            progress_color=theme.ACCENT, command=self.onToggleAlertCave,
        )
        self.alertCaveSwitch.grid(row=0, column=0, sticky='w', pady=2)
        if self.context.context['alert']['cave']:
            self.alertCaveSwitch.select()
        self.alertSaySwitch = customtkinter.CTkSwitch(
            card.body, text='"?" an Spieler senden',
            progress_color=theme.ACCENT, command=self.onToggleAlertSayPlayer,
        )
        self.alertSaySwitch.grid(row=1, column=0, sticky='w', pady=2)
        if self.context.context['alert']['sayPlayer']:
            self.alertSaySwitch.select()

    def _buildClearStatsCard(self):
        card = Card(self.scroll, 'Clear Stats', toggled=self.onTogglePoison,
                    toggle_initial=self.context.context['clear_stats']['poison'])
        card.grid(row=3, column=1, padx=(5, 0), pady=(0, 10), sticky='nsew')
        customtkinter.CTkLabel(
            card.body, text='Poison Hotkey', font=theme.font(12),
            text_color=theme.MUTED,
        ).grid(row=0, column=0, sticky='w', pady=3)
        HotkeyEntry(
            card.body,
            initial=self.context.context['clear_stats']['poison_hotkey'],
            on_change=self.context.setClearStatsPoisonHotkey, width=110,
        ).grid(row=0, column=1, sticky='e', pady=3)

    def _buildBackupCard(self):
        card = Card(self.scroll, 'Backup', 'Konfiguration als Datei sichern')
        card.grid(row=4, column=0, columnspan=2, pady=(0, 10), sticky='ew')
        card.body.grid_columnconfigure((0, 1), weight=1)
        customtkinter.CTkButton(
            card.body, text='Config speichern', height=34, corner_radius=8,
            fg_color=theme.ELEVATED, hover_color=theme.BORDER,
            text_color=theme.TEXT, command=self.saveCfg,
        ).grid(row=0, column=0, padx=(0, 4), sticky='ew')
        customtkinter.CTkButton(
            card.body, text='Config laden', height=34, corner_radius=8,
            fg_color=theme.ELEVATED, hover_color=theme.BORDER,
            text_color=theme.TEXT, command=self.loadCfg,
        ).grid(row=0, column=1, padx=(4, 0), sticky='ew')

    def _buildAlliesCard(self):
        card = Card(self.scroll, 'Verbündete & Feinde',
                    'Feinde lösen sofort Alarm aus, Verbündete keine Pause')
        card.grid(row=5, column=0, columnspan=2, pady=(0, 10), sticky='ew')
        card.body.grid_columnconfigure((0, 1), weight=1)
        self.allyList = self._nameColumn(
            card.body, 0, 'Verbündete',
            self.context.context.get('allies', []),
            self.context.addAlly, self.context.removeAlly)
        self.enemyList = self._nameColumn(
            card.body, 1, 'Feinde',
            self.context.context.get('enemies', []),
            self.context.addEnemy, self.context.removeEnemy)

    def _nameColumn(self, parent, column, title, names, onAdd, onRemove):
        frame = customtkinter.CTkFrame(parent, fg_color='transparent')
        frame.grid(row=0, column=column, padx=(0 if column else 8,
                                               8 if column else 0),
                   sticky='nsew')
        frame.grid_columnconfigure(0, weight=1)
        customtkinter.CTkLabel(
            frame, text=title, font=theme.font(12, 'bold'),
        ).grid(row=0, column=0, sticky='w')
        listLabel = customtkinter.CTkLabel(
            frame, text='\n'.join(names) if names else '—',
            font=theme.font(11), text_color=theme.MUTED, justify='left',
        )
        listLabel.grid(row=1, column=0, columnspan=2, sticky='w', pady=2)
        entry = customtkinter.CTkEntry(
            frame, placeholder_text='Spielername …', font=theme.font(11),
        )
        entry.grid(row=2, column=0, sticky='ew', padx=(0, 4), pady=2)

        def add():
            name = entry.get().strip()
            if name and name not in names:
                onAdd(name)
                entry.delete(0, 'end')
                listLabel.configure(
                    text='\n'.join(self.context.context.get(
                        'allies' if title == 'Verbündete' else 'enemies', [])))

        def remove():
            name = entry.get().strip()
            if name in names:
                onRemove(name)
                entry.delete(0, 'end')
                listLabel.configure(
                    text='\n'.join(self.context.context.get(
                        'allies' if title == 'Verbündete' else 'enemies',
                        [])) or '—')

        customtkinter.CTkButton(
            frame, text='+', width=34, height=28, corner_radius=8,
            fg_color=theme.ACCENT, hover_color=theme.ACCENT_HOVER,
            command=add,
        ).grid(row=2, column=1, sticky='e', pady=2)
        customtkinter.CTkButton(
            frame, text='–', width=34, height=28, corner_radius=8,
            fg_color=theme.ELEVATED, hover_color=theme.DANGER,
            text_color=theme.MUTED, command=remove,
        ).grid(row=3, column=1, sticky='e', pady=2)
        return listLabel

    def _buildDesignCard(self):
        card = Card(self.scroll, 'Design',
                    'Akzentfarbe, Hell/Dunkel und Glas-Stärke — gilt sofort')
        card.grid(row=0, column=0, columnspan=2, pady=(0, 10), sticky='ew')
        card.body.grid_columnconfigure(0, weight=0)  # Swatches linksbuendig
        current = self.context.getUiTheme()
        for column, name in enumerate(theme.THEMES):
            selected = name == current
            swatch = customtkinter.CTkButton(
                card.body, text='', width=46, height=46, corner_radius=23,
                fg_color=theme.THEMES[name]['accent'],
                hover_color=theme.THEMES[name]['hover'],
                border_width=3,
                border_color='#FFFFFF' if selected else theme.GLASS_EDGE,
                command=lambda n=name: self._pickTheme(n),
            )
            swatch.grid(row=0, column=column, padx=7, pady=4, sticky='w')

        customtkinter.CTkLabel(
            card.body, text='Darstellung', font=theme.font(12),
            text_color=theme.MUTED,
        ).grid(row=1, column=0, sticky='w', padx=7, pady=(10, 2))
        self.modeSegment = customtkinter.CTkSegmentedButton(
            card.body, values=['Dunkel', 'Hell'], command=self._pickMode,
            selected_color=theme.ACCENT,
            selected_hover_color=theme.ACCENT_HOVER,
        )
        self.modeSegment.set(
            'Hell' if self.context.getUiMode() == 'hell' else 'Dunkel')
        self.modeSegment.grid(
            row=1, column=1, columnspan=5, sticky='w', pady=(10, 2))

        customtkinter.CTkLabel(
            card.body, text='Glas-Stärke', font=theme.font(12),
            text_color=theme.MUTED,
        ).grid(row=2, column=0, sticky='w', padx=7, pady=(8, 4))
        self.glassValue = customtkinter.CTkLabel(
            card.body, text=f'{self.context.getUiGlass()} %',
            font=theme.font(12, 'bold'), text_color=theme.ACCENT, width=52,
        )
        self.glassValue.grid(row=2, column=5, sticky='e', pady=(8, 4))
        self._glassJob = None
        self.glassSlider = customtkinter.CTkSlider(
            card.body, from_=0, to=100,
            number_of_steps=20, command=self._slideGlass,
            progress_color=theme.ACCENT, button_color=theme.ACCENT,
            button_hover_color=theme.ACCENT_HOVER,
        )
        self.glassSlider.set(self.context.getUiGlass())
        self.glassSlider.grid(
            row=2, column=1, columnspan=4, sticky='ew', pady=(8, 4))

    def _pickTheme(self, name):
        app = self.winfo_toplevel()
        if hasattr(app, 'applyTheme'):
            # Baut alle Seiten (inkl. dieser) mit der neuen Farbe neu.
            app.applyTheme(name)

    def _pickMode(self, label):
        self.context.setUiMode('hell' if label == 'Hell' else 'dunkel')
        app = self.winfo_toplevel()
        if hasattr(app, 'applyDesign'):
            app.applyDesign()

    def _slideGlass(self, raw):
        value = int(raw)
        self.glassValue.configure(text=f'{value} %')
        self.context.setUiGlass(value)
        # Entprellt neu aufbauen, sonst ruckelt der Regler.
        if self._glassJob is not None:
            self.after_cancel(self._glassJob)
        self._glassJob = self.after(400, self._applyGlass)

    def _applyGlass(self):
        self._glassJob = None
        app = self.winfo_toplevel()
        if hasattr(app, 'applyDesign'):
            app.applyDesign()

    # ------------------------------------------------------------------ Hardware
    def refreshHardware(self):
        self.displays = getDisplays()
        labels = [display['label'] for display in self.displays]
        self.displayCombobox.configure(values=labels or ['Keine Bildschirme erkannt'])
        if self.displays:
            self.displayCombobox.set(getSelectedDisplay()['label'])
        method = getInputMethod()
        self.inputMethodCombobox.set(
            LABEL_BY_METHOD.get(method, 'Mac (Bluetooth)'))
        connected = isArduinoAvailable()
        if method == INPUT_METHOD_MAC_BT:
            text = 'Mac-Bluetooth verbunden' if connected else 'Mac nicht gekoppelt / Bridge fehlt'
        else:
            text = 'Arduino-HID verbunden' if connected else 'Arduino-HID fehlt'
        self.arduinoStatusLabel.configure(
            text=text,
            text_color=theme.SUCCESS if connected else theme.DANGER,
        )

    def onChangeInputMethod(self, label):
        method = INPUT_METHOD_LABELS.get(label)
        if method is not None:
            self.context.setInputMethod(method)
            self.refreshHardware()

    def onChangeDisplay(self, label):
        selected = next(
            (display for display in self.displays if display['label'] == label),
            None,
        )
        if selected is not None:
            saveSelectedDisplay(selected['device'])

    def getGameWindows(self):
        def enum_windows_callback(hwnd, results):
            if win32gui.IsWindowVisible(hwnd):
                window_title = win32gui.GetWindowText(hwnd)
                if re.search(r'Windowed', window_title, re.IGNORECASE) or re.match(
                    r'^Tibia(?:\s+-.*)?$', window_title, re.IGNORECASE
                ):
                    results.append(window_title)
        results = []
        win32gui.EnumWindows(enum_windows_callback, results)
        return results

    def refreshWindows(self):
        self.windowsCombobox.configure(values=self.getGameWindows())

    def onChangeWindow(self, _):
        selectedWindow = self.windowsCombobox.get()
        self.context.context['window'] = gw.getWindowsWithTitle(selectedWindow)[
            0]

    # ------------------------------------------------------------------ Toggles
    def onToggleAutoHur(self):
        self.context.toggleAutoHur(not self.context.context['auto_hur']['enabled'])

    def onToggleAutoHurPz(self):
        self.context.toggleAutoHurPz(self.pzSwitch.get() == 1)

    def onToggleAlert(self):
        self.context.toggleAlert(not self.context.context['alert']['enabled'])

    def onToggleAlertCave(self):
        self.context.toggleAlertCave(self.alertCaveSwitch.get() == 1)

    def onToggleAlertSayPlayer(self):
        self.context.toggleAlertSayPlayer(self.alertSaySwitch.get() == 1)

    def onTogglePoison(self):
        self.context.toggleClearStatsPoison(
            not self.context.context['clear_stats']['poison'])

    def setHurSpell(self):
        self.context.setAutoHurSpell(self.hurSpellPicker.get())

    # ------------------------------------------------------------------ Backup
    def saveCfg(self):
        file = filedialog.asksaveasfilename(
            defaultextension='.pilotng',
            filetypes=[('PilotNG Cfg', '*.pilotng'), ('Alle Dateien', '*.*')],
        )
        if file:
            cfg = {
                'ng_backpacks': self.context.context['ng_backpacks'],
                'general_hotkeys': self.context.context['general_hotkeys'],
                'auto_hur': self.context.context['auto_hur'],
                'alert': self.context.context['alert'],
                'clear_stats': self.context.context['clear_stats'],
                'ng_comboSpells': self.context.context['ng_comboSpells'],
                'healing': self.context.context['healing'],
            }
            with open(file, 'w') as f:
                json.dump(cfg, f, indent=4)
            messagebox.showinfo('PilotNG', 'Config gespeichert!')

    def loadCfg(self):
        file = filedialog.askopenfilename(
            defaultextension='.pilotng',
            filetypes=[('PilotNG Cfg', '*.pilotng'), ('Alle Dateien', '*.*')],
        )
        if file:
            with open(file, 'r') as f:
                cfg = json.load(f)
                self.context.loadCfg(cfg)
            messagebox.showinfo('PilotNG', 'Config geladen!')
