#!/usr/bin/env python3
"""
Moderne Benutzerverwaltung für Void Linux
Erstellt mit Python und GTK4/Adw
"""

import gi
import os
import pwd
import grp
import subprocess
import threading

gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, GObject, Gio, GLib, Gdk

# --- KONFIGURATION ---
# Hier können die visuellen Einstellungen einfach angepasst werden.

# 1. Transparenz (Alpha-Wert: 1.0 = undurchsichtig, 0.0 = komplett durchsichtig)
LIGHT_THEME_BG = "rgba(248, 248, 248, 0.5)"
DARK_THEME_BG = "rgba(40, 42, 54, 0.5)"

# 2. Pfad zum Fenster-Icon für die Taskleiste
ICON_PATH = "/usr/local/bin/user/icons/user.png"
# --- ENDE DER KONFIGURATION ---


class UserModel(GObject.Object):
    """Model für Benutzerinformationen"""
    
    def __init__(self, username, uid, gid, home, shell, gecos=""):
        super().__init__()
        self.username = username
        self.uid = uid
        self.gid = gid
        self.home = home
        self.shell = shell
        self.gecos = gecos
        self.groups = self.get_user_groups()
    
    def get_user_groups(self):
        """Ermittelt alle Gruppen des Benutzers effizienter."""
        try:
            primary_group_name = grp.getgrgid(self.gid).gr_name
            user_groups = {primary_group_name}
            for group in grp.getgrall():
                if self.username in group.gr_mem:
                    user_groups.add(group.gr_name)
            return sorted(list(user_groups))
        except (KeyError, Exception):
            return []

class UserManagementWindow(Gtk.ApplicationWindow):
    """Hauptfenster der Benutzerverwaltung"""
    
    def __init__(self, app):
        super().__init__(application=app)
        self.set_title("Void Linux Benutzerverwaltung")
        self.set_default_size(1000, 700)
        
        # KORREKTUR: Der korrekte Methodenname in GTK4 ist 'set_icon_name'.
        # Er kann auch einen absoluten Dateipfad verarbeiten.
        if os.path.exists(ICON_PATH):
            self.set_icon_name(ICON_PATH)
        
        try:
            display = Gdk.Display.get_default()
            visual = display.get_rgba_visual()
            if visual:
                self.set_visual(visual)
        except AttributeError:
            pass

        self.add_css_class("transparent-window")
        
        self.setup_ui()
        self.load_users()
        
    def setup_ui(self):
        header_bar = Adw.HeaderBar()
        self.set_titlebar(header_bar)
        
        self.add_button = Gtk.Button(label="Benutzer hinzufügen", icon_name="list-add-symbolic")
        self.add_button.connect("clicked", self.on_add_user)
        header_bar.pack_start(self.add_button)
        
        refresh_button = Gtk.Button(icon_name="view-refresh-symbolic", tooltip_text="Aktualisieren")
        refresh_button.connect("clicked", self.on_refresh)
        header_bar.pack_end(refresh_button)

        theme_button = Gtk.MenuButton(icon_name="open-menu-symbolic", tooltip_text="Darstellung anpassen")
        
        check_system = Gtk.CheckButton(label="System-Stil")
        check_light = Gtk.CheckButton(label="Heller Stil", group=check_system)
        check_dark = Gtk.CheckButton(label="Dunkler Stil", group=check_system)

        style_manager = self.get_application().get_style_manager()
        scheme = style_manager.get_color_scheme()
        if scheme == Adw.ColorScheme.FORCE_LIGHT: check_light.set_active(True)
        elif scheme == Adw.ColorScheme.FORCE_DARK: check_dark.set_active(True)
        else: check_system.set_active(True)

        check_system.connect("toggled", self.on_theme_change, Adw.ColorScheme.DEFAULT)
        check_light.connect("toggled", self.on_theme_change, Adw.ColorScheme.FORCE_LIGHT)
        check_dark.connect("toggled", self.on_theme_change, Adw.ColorScheme.FORCE_DARK)
        
        theme_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, margin_top=6, margin_bottom=6, margin_start=6, margin_end=6)
        theme_box.append(check_system); theme_box.append(check_light); theme_box.append(check_dark)
        
        theme_popover = Gtk.Popover(child=theme_box)
        theme_button.set_popover(theme_popover)
        header_bar.pack_end(theme_button)
        
        self.toast_overlay = Adw.ToastOverlay()
        main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12, margin_top=12, margin_bottom=12, margin_start=12, margin_end=12)
        
        self.toast_overlay.set_child(main_box)
        self.set_child(self.toast_overlay)
        
        self.setup_user_list(main_box)
        self.setup_details_panel(main_box)

    def on_theme_change(self, button, scheme):
        if button.get_active():
            style_manager = self.get_application().get_style_manager()
            style_manager.set_color_scheme(scheme)
    
    def setup_user_list(self, parent):
        left_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, width_request=300)
        
        search_entry = Gtk.SearchEntry(placeholder_text="Benutzer suchen...")
        search_entry.connect("search-changed", self.on_search_changed)
        left_box.append(search_entry)
        
        scrolled = Gtk.ScrolledWindow(vexpand=True)
        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        
        self.user_listbox = Gtk.ListBox(selection_mode=Gtk.SelectionMode.SINGLE, css_classes=["navigation-sidebar"])
        self.user_listbox.connect("row-selected", self.on_user_selected)
        
        scrolled.set_child(self.user_listbox)
        left_box.append(scrolled)
        parent.append(left_box)
    
    def setup_details_panel(self, parent):
        self.details_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12, hexpand=True)
        self.welcome_group = Adw.PreferencesGroup(title="Willkommen", description="Wählen Sie einen Benutzer aus der Liste aus, um Details anzuzeigen")
        self.details_box.append(self.welcome_group)
        parent.append(self.details_box)
    
    def load_users(self):
        while row := self.user_listbox.get_first_child(): self.user_listbox.remove(row)
        
        users = [UserModel(u.pw_name, u.pw_uid, u.pw_gid, u.pw_dir, u.pw_shell, u.pw_gecos) for u in pwd.getpwall() if u.pw_uid >= 1000 or u.pw_name in ['root', 'nobody']]
        users.sort(key=lambda u: u.username)
        
        for user in users: self.add_user_row(user)
    
    def add_user_row(self, user):
        user_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12, margin_top=8, margin_bottom=8, margin_start=12, margin_end=12)
        
        avatar = Adw.Avatar(text=user.username[0].upper(), size=32)
        info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2, hexpand=True)
        username_label = Gtk.Label(label=user.username, halign=Gtk.Align.START, css_classes=["heading"])
        uid_label = Gtk.Label(label=f"UID: {user.uid}", halign=Gtk.Align.START, css_classes=["dim-label", "caption"])
        info_box.append(username_label); info_box.append(uid_label)
        
        icon_name, tooltip = ("emblem-system-symbolic", "System-Benutzer")
        if user.username == "root": icon_name, tooltip = ("security-high-symbolic", "Administrator")
        elif user.uid >= 1000: icon_name, tooltip = ("user-available-symbolic", "Regulärer Benutzer")
        status_icon = Gtk.Image.new_from_icon_name(icon_name)
        status_icon.set_tooltip_text(tooltip)

        user_box.append(avatar); user_box.append(info_box); user_box.append(status_icon)
        
        row_item = Gtk.ListBoxRow(child=user_box)
        row_item.user_data = user
        
        self.user_listbox.append(row_item)
    
    def on_user_selected(self, listbox, row):
        if self.welcome_group.get_parent(): self.details_box.remove(self.welcome_group)
        if not row:
            self.details_box.append(self.welcome_group)
            return
        self.show_user_details(row.user_data)
    
    def show_user_details(self, user):
        while child := self.details_box.get_first_child(): self.details_box.remove(child)
        
        header_group = Adw.PreferencesGroup(title=f"Benutzer: {user.username}", description=user.gecos or "Keine Beschreibung")
        
        basic_group = Adw.PreferencesGroup(title="Basis Informationen")
        try: gid_subtitle = f"{user.gid} ({grp.getgrgid(user.gid).gr_name})"
        except KeyError: gid_subtitle = str(user.gid)
        basic_group.add(Adw.ActionRow(title="Benutzer-ID (UID)", subtitle=str(user.uid)))
        basic_group.add(Adw.ActionRow(title="Gruppen-ID (GID)", subtitle=gid_subtitle))
        basic_group.add(Adw.ActionRow(title="Home-Verzeichnis", subtitle=user.home))
        basic_group.add(Adw.ActionRow(title="Standard-Shell", subtitle=user.shell))
        
        groups_group = Adw.PreferencesGroup(title="Gruppenmitgliedschaften")
        groups_text = ", ".join(user.groups) or "Keine zusätzlichen Gruppen"
        groups_row = Adw.ActionRow(title="Mitglied in Gruppen", subtitle=groups_text, subtitle_lines=0)
        groups_group.add(groups_row)

        actions_group = Adw.PreferencesGroup(title="Aktionen")
        
        button_size_group = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)

        if user.username != "root":
            password_row = Adw.ActionRow(title="Passwort ändern", subtitle="Neues Passwort für diesen Benutzer setzen")
            password_button = Gtk.Button(label="Ändern", css_classes=["suggested-action"])
            password_button.connect("clicked", lambda _, u=user: self.change_password(u))
            button_size_group.add_widget(password_button)
            password_row.add_suffix(password_button)
            actions_group.add(password_row)
        
        groups_edit_row = Adw.ActionRow(title="Gruppen bearbeiten", subtitle="Gruppenmitgliedschaften verwalten")
        groups_button = Gtk.Button(label="Bearbeiten")
        groups_button.connect("clicked", lambda _, u=user: self.edit_groups(u))
        button_size_group.add_widget(groups_button)
        groups_edit_row.add_suffix(groups_button)
        actions_group.add(groups_edit_row)
        
        if user.username != "root":
            delete_row = Adw.ActionRow(title="Benutzer löschen", subtitle="Benutzer permanent entfernen")
            delete_button = Gtk.Button(label="Löschen", css_classes=["destructive-action"])
            delete_button.connect("clicked", lambda _, u=user: self.delete_user(u))
            button_size_group.add_widget(delete_button)
            delete_row.add_suffix(delete_button)
            actions_group.add(delete_row)

        for group in [header_group, basic_group, groups_group, actions_group]: self.details_box.append(group)
    
    def change_password(self, user):
        dialog = Adw.MessageDialog.new(self, f"Passwort für {user.username} ändern", "Geben Sie das neue Passwort ein:")
        dialog.add_response("cancel", "Abbrechen"); dialog.add_response("change", "Ändern")
        dialog.set_response_appearance("change", Adw.ResponseAppearance.SUGGESTED)
        
        password_entry = Adw.PasswordEntryRow(title="Neues Passwort")
        confirm_entry = Adw.PasswordEntryRow(title="Passwort bestätigen")
        
        content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        content_box.append(password_entry); content_box.append(confirm_entry)
        dialog.set_extra_child(content_box)
        
        def on_response(d, response):
            if response == "change":
                password = password_entry.get_text()
                if not password or password != confirm_entry.get_text():
                    self.show_error("Passwörter sind leer oder stimmen nicht überein"); return
                self.execute_command_async(["passwd", user.username], input_text=f"{password}\n{password}\n", success_msg=f"Passwort für {user.username} erfolgreich geändert")
        
        dialog.connect("response", on_response); dialog.present()

    def edit_groups(self, user):
        dialog = Gtk.Dialog(transient_for=self, title=f"Gruppen für {user.username}", default_width=400, default_height=500)
        dialog.set_titlebar(Adw.HeaderBar())

        scrolled = Gtk.ScrolledWindow(vexpand=True)
        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        
        groups_listbox = Gtk.ListBox(css_classes=["boxed-list"])
        self.group_switches = {}

        all_groups = sorted([g.gr_name for g in grp.getgrall() if g.gr_name != user.username])
        for group_name in all_groups:
            row = Adw.ActionRow(title=group_name)
            switch = Gtk.Switch(active=(group_name in user.groups), valign=Gtk.Align.CENTER)
            self.group_switches[group_name] = switch
            row.add_suffix(switch)
            groups_listbox.append(row)
        scrolled.set_child(groups_listbox)

        button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12, halign=Gtk.Align.END)
        cancel_button = Gtk.Button(label="Abbrechen")
        cancel_button.connect("clicked", lambda _: dialog.close())
        save_button = Gtk.Button(label="Speichern", css_classes=["suggested-action"])
        save_button.connect("clicked", lambda _: self.save_group_changes(user, dialog))
        button_box.append(cancel_button); button_box.append(save_button)

        content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12, margin_top=12, margin_bottom=12, margin_start=12, margin_end=12)
        content_box.append(scrolled); content_box.append(button_box)
        dialog.set_child(content_box); dialog.present()

    def save_group_changes(self, user, dialog):
        new_groups = {name for name, switch in self.group_switches.items() if switch.get_active()}
        current_groups = set(user.groups)
        
        for group in (new_groups - current_groups): self.execute_command_async(["gpasswd", "-a", user.username, group])
        for group in (current_groups - new_groups):
            try:
                if grp.getgrgid(user.gid).gr_name != group: self.execute_command_async(["gpasswd", "-d", user.username, group])
            except KeyError: pass
        
        dialog.close(); GLib.timeout_add(500, self.on_refresh)
    
    def delete_user(self, user):
        dialog = Adw.MessageDialog.new(self, f"Benutzer {user.username} löschen?", f"Sind Sie sicher, dass Sie '{user.username}' permanent löschen möchten? Das Home-Verzeichnis wird ebenfalls entfernt.")
        dialog.add_response("cancel", "Abbrechen"); dialog.add_response("delete", "Löschen")
        dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
        dialog.connect("response", lambda d, r: self.execute_command_async(["userdel", "-r", user.username], success_msg=f"Benutzer {user.username} erfolgreich gelöscht") if r == "delete" else None)
        dialog.present()
    
    def on_add_user(self, button):
        dialog = Gtk.Dialog(transient_for=self, title="Neuen Benutzer hinzufügen", default_width=400)
        dialog.set_titlebar(Adw.HeaderBar())
        
        form_group = Adw.PreferencesGroup(title="Benutzer-Details")
        username_row = Adw.EntryRow(title="Benutzername"); fullname_row = Adw.EntryRow(title="Vollständiger Name")
        password_row = Adw.PasswordEntryRow(title="Passwort")
        for row in [username_row, fullname_row, password_row]: form_group.add(row)
        
        button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12, halign=Gtk.Align.END)
        cancel_button = Gtk.Button(label="Abbrechen")
        cancel_button.connect("clicked", lambda _: dialog.close())
        create_button = Gtk.Button(label="Erstellen", css_classes=["suggested-action"])
        create_button.connect("clicked", lambda _: self.create_user(username_row.get_text(), fullname_row.get_text(), password_row.get_text(), dialog))
        button_box.append(cancel_button); button_box.append(create_button)
        
        content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12, margin_top=12, margin_bottom=12, margin_start=12, margin_end=12)
        content_box.append(form_group); content_box.append(button_box)
        dialog.set_child(content_box); dialog.present()
    
    def create_user(self, username, fullname, password, dialog):
        if not username or not password: self.show_error("Benutzername und Passwort sind erforderlich"); return
        
        cmd = ["useradd", "-m", "-s", "/bin/bash"]
        if fullname: cmd.extend(["-c", fullname])
        cmd.append(username)
        
        self.execute_command_async(cmd, success_callback=lambda: (
            self.execute_command_async(["passwd", username], input_text=f"{password}\n{password}\n", success_msg=f"Benutzer {username} erfolgreich erstellt"),
            dialog.close()
        ))
    
    def execute_command_async(self, cmd, input_text=None, success_msg=None, success_callback=None):
        def run_in_thread():
            try:
                process = subprocess.run(["pkexec"] + cmd, input=input_text, capture_output=True, text=True, check=False)
                GLib.idle_add(self.command_finished, process, success_msg, success_callback)
            except FileNotFoundError: GLib.idle_add(self.show_error, "Fehler: 'pkexec' wurde nicht gefunden. Ist Polkit installiert?")
            except Exception as e: GLib.idle_add(self.show_error, f"Ein unerwarteter Fehler ist aufgetreten: {e}")
        threading.Thread(target=run_in_thread, daemon=True).start()
    
    def command_finished(self, process, success_msg, success_callback):
        if process.returncode == 0:
            if success_msg: self.show_success(success_msg)
            if success_callback: success_callback()
            self.on_refresh()
        elif process.returncode not in (126, 127): self.show_error(f"Fehler: {process.stderr.strip()}")

    def show_error(self, message): self.toast_overlay.add_toast(Adw.Toast.new(message))
    def show_success(self, message): self.toast_overlay.add_toast(Adw.Toast.new(f"✓ {message}"))
    def on_search_changed(self, search_entry): self.user_listbox.set_filter_func(lambda row: search_entry.get_text().lower() in row.user_data.username.lower())
    
    def on_refresh(self, button=None):
        selected_row = self.user_listbox.get_selected_row()
        selected_username = selected_row.user_data.username if selected_row else None
        self.load_users()
        if selected_username:
            for row in self.user_listbox:
                if row.user_data.username == selected_username:
                    self.user_listbox.select_row(row); break

class UserManagementApp(Adw.Application):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
    
    def do_activate(self):
        css_provider = Gtk.CssProvider()
        css_data = f"""
        window.transparent-window.light {{ background-color: {LIGHT_THEME_BG}; }}
        window.transparent-window.dark {{ background-color: {DARK_THEME_BG}; }}
        """
        css_provider.load_from_data(css_data.encode())
        Gtk.StyleContext.add_provider_for_display(
            Gdk.Display.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )
        
        win = UserManagementWindow(self)
        win.present()

if __name__ == "__main__":
    app_id = "com.voidlinux.usermanager"
    app = UserManagementApp(application_id=app_id)
    exit_code = app.run([])
