import json
import os
import re
import shutil
import subprocess
import threading
import time
import urllib.request
from typing import Any, Dict, List, Optional

TARGET_ROOT = "/mnt/void"


class InstallationBackend:
    def __init__(self, target_root: str = TARGET_ROOT, log_callback=None):
        self.target_root = target_root
        self.log_callback = log_callback or self._default_log
        self._binds_active = False

    def _default_log(self, msg: str):
        print(f"[InstallationBackend] {msg}")

    def _log(self, msg: str):
        self.log_callback(msg)

        # Log auch in Datei schreiben (für Debugging nach Install)
        try:
            if os.path.exists(self.target_root):
                log_dir = os.path.join(self.target_root, "var/log")
                if not os.path.exists(log_dir):
                    return
                
                with open(os.path.join(log_dir, "void-installer.log"), "a") as f:
                    f.write(msg + "\n")
        except:
            pass

    def _get_partition_device(self, device: str, part_num: int) -> str:
        if device.startswith("/dev/nvme"):
            return f"{device}p{part_num}"
        else:
            return f"{device}{part_num}"

    def _run(
        self, cmd: List[str], check=True, capture=False, chroot=False
    ) -> subprocess.CompletedProcess:
        if chroot:
            cmd = ["chroot", self.target_root] + cmd
        self._log(f"$ {' '.join(cmd)}")
        try:
            if capture:
                result = subprocess.run(
                    cmd, check=check, text=True, capture_output=True
                )
                if result.stderr:
                    self._log(f"STDERR: {result.stderr}")
                return result
            else:
                result = subprocess.run(
                    cmd, check=check, text=True, capture_output=True
                )
                if result.stdout:
                    self._log(f"STDOUT: {result.stdout}")
                if result.stderr:
                    self._log(f"STDERR: {result.stderr}")
                return result
        except subprocess.CalledProcessError as e:
            out = (getattr(e, "stdout", "") or "") + (getattr(e, "stderr", "") or "")
            self._log(f"!! {_('BEFEHL FEHLGESCHLAGEN')} (Exit Code {e.returncode}): {e}")
            if out.strip():
                self._log(f"!! OUTPUT: {out}")
            if check:
                raise
            return e

    def _is_mounted(self, path: str) -> bool:
        try:
            with open("/proc/mounts", "r") as f:
                for line in f:
                    parts = line.split()
                    if len(parts) >= 2 and parts[1] == path:
                        return True
                return False
        except Exception as e:
            self._log(_("Warnung: Konnte /proc/mounts nicht lesen: {}").format(e))
            return False

    def _mount_partition_safe(self, device: str, mountpoint: str, partition_name: str):
        self._log(_("Mounte {}-Partition: {} → {}").format(partition_name, device, mountpoint))

        recovery_strategies = [
            (_("Standard Mount"), self._try_standard_mount),
            (_("Force Mount mit fsck"), self._try_mount_with_fsck),
            (_("Mount mit explizitem Dateisystem"), self._try_mount_explicit_fs),
            (_("Read-only Mount"), self._try_readonly_mount),
        ]

        for strategy_name, strategy_func in recovery_strategies:
            try:
                self._log(_("Versuche {} für {}").format(strategy_name, device))
                if strategy_func(device, mountpoint, partition_name):
                    self._log(
                        _("✓ {}-Partition mit {} erfolgreich gemountet").format(partition_name, strategy_name)
                    )
                    return
            except Exception as e:
                self._log(_("✗ {} fehlgeschlagen: {}").format(strategy_name, e))
                continue

        raise Exception(
            _("Alle Mount-Strategien für {}-Partition fehlgeschlagen").format(partition_name)
        )

    def _try_standard_mount(
        self, device: str, mountpoint: str, partition_name: str
    ) -> bool:
        if not self._wait_for_device(device):
            return False
        if not os.path.exists(device):
            self._log(_("Gerät {} existiert nicht").format(device))
            return False
        if self._is_mounted(mountpoint):
            self._log(
                _("Warnung: {} ist bereits gemountet, versuche unmount...").format(mountpoint)
            )
            self._run(["umount", mountpoint], check=False)
        os.makedirs(mountpoint, exist_ok=True)
        result = self._run(["file", "-s", device], capture=True, check=False)
        fs_info = result.stdout.strip()
        self._log(_("Dateisystem auf {}: {}").format(device, fs_info))
        self._run(["mount", device, mountpoint])
        return self._is_mounted(mountpoint)

    def _try_mount_with_fsck(
        self, device: str, mountpoint: str, partition_name: str
    ) -> bool:
        try:
            self._log(_("Führe Dateisystem-Check durch auf {}").format(device))
            self._run(["fsck", "-y", device], check=False)
            return self._try_standard_mount(device, mountpoint, partition_name)
        except:
            return False

    def _try_mount_explicit_fs(
        self, device: str, mountpoint: str, partition_name: str
    ) -> bool:
        try:
            result = self._run(
                ["blkid", "-s", "TYPE", "-o", "value", device],
                capture=True,
                check=False,
            )
            fs_type = result.stdout.strip()
            if fs_type:
                self._log(_("Versuche Mount mit explizitem FS-Typ: {}").format(fs_type))
                os.makedirs(mountpoint, exist_ok=True)
                self._run(["mount", "-t", fs_type, device, mountpoint])
                return self._is_mounted(mountpoint)
        except:
            pass
        return False

    def _try_readonly_mount(
        self, device: str, mountpoint: str, partition_name: str
    ) -> bool:
        try:
            self._log(_("Versuche Read-Only Mount für {}").format(device))
            os.makedirs(mountpoint, exist_ok=True)
            self._run(["mount", "-o", "ro", device, mountpoint])
            if self._is_mounted(mountpoint):
                self._log(_("Warnung: {} nur als Read-Only gemountet!").format(partition_name))
                return True
        except:
            pass
        return False

    def _wait_for_device(self, device: str, max_wait: int = 30):
        self._log(_("Warte auf Gerät {}...").format(device))
        for attempt in range(max_wait):
            if os.path.exists(device):
                try:
                    with open(device, "rb") as f:
                        f.read(512)
                    self._log(_("Gerät {} ist bereit nach {} Sekunden").format(device, attempt + 1))
                    return True
                except (OSError, IOError) as e:
                    if attempt < max_wait - 1:
                        time.sleep(1)
                        continue
                    else:
                        self._log(_("Gerät {} nicht lesbar nach {}s: {}").format(device, max_wait, e))
                        break
            else:
                if attempt < max_wait - 1:
                    time.sleep(1)
                else:
                    self._log(_("Gerät {} nicht verfügbar nach {}s").format(device, max_wait))
                    break
        return False

    def _verify_filesystem(self, device: str, expected_fs: str = None) -> bool:
        try:
            self._log(_("Verifiziere Dateisystem auf {}...").format(device))
            result = self._run(["file", "-s", device], capture=True, check=False)
            fs_info = result.stdout.strip().lower()
            if (
                "filesystem" not in fs_info
                and "ext" not in fs_info
                and "btrfs" not in fs_info
            ):
                self._log(
                    _("Warnung: Keine erkennbare Dateisystem-Signatur auf {}").format(device)
                )
                return False
            try:
                result = self._run(
                    ["blkid", "-s", "TYPE", "-o", "value", device],
                    capture=True,
                    check=False,
                )
                fs_type = result.stdout.strip()
                if fs_type:
                    self._log(_("Erkanntes Dateisystem: {}").format(fs_type))
                    if expected_fs and fs_type != expected_fs:
                        self._log(
                            _("Warnung: Erwartet {}, gefunden {}").format(expected_fs, fs_type)
                        )
                else:
                    self._log(_("Kein Dateisystem-Typ von blkid erkannt"))
            except:
                self._log(_("blkid konnte Dateisystem-Typ nicht ermitteln"))
            return True
        except Exception as e:
            self._log(_("Dateisystem-Verifikation fehlgeschlagen: {}").format(e))
            return False

    def _unmount_all_on_device(self, device: str):
        self._log(_("Prüfe und deaktiviere alle Mounts und Volumes auf {}...").format(device))
        try:
            out = self._run(
                ["lsblk", "-Jpno", "NAME,TYPE,FSTYPE,MOUNTPOINT", device], capture=True
            ).stdout
            data = json.loads(out)
            partitions = []

            def find_partitions(node):
                if node.get("type") == "part":
                    partitions.append(node)
                for child in node.get("children", []):
                    find_partitions(child)

            for dev_info in data.get("blockdevices", []):
                find_partitions(dev_info)

            for part in reversed(partitions):
                path = part["name"]
                mountpoint = part.get("mountpoint")
                if mountpoint and mountpoint != "":
                    self._log(f"Unmounting {path} from {mountpoint}")
                    self._run(["umount", "-f", "-l", path], check=False)
                if part.get("fstype") == "swap":
                    self._log(f"Disabling swap on {path}")
                    self._run(["swapoff", path], check=False)

            for part in reversed(partitions):
                path = part["name"]
                if self._is_mounted(path):
                    self._log(f"Force unmounting {path}")
                    self._run(["umount", "-f", "-l", path], check=False)

            try:
                out = self._run(
                    ["lsblk", "-Jpno", "NAME,TYPE,FSTYPE,MOUNTPOINT", device],
                    capture=True,
                ).stdout
                data = json.loads(out)
                partitions = []

                def find_partitions(node):
                    if node.get("type") == "part":
                        partitions.append(node)
                    for child in node.get("children", []):
                        find_partitions(child)

                for dev_info in data.get("blockdevices", []):
                    find_partitions(dev_info)

                self._log(_("Gefundene Partitionen: {}").format([p['name'] for p in partitions]))

                for partition in partitions:
                    part_device = partition["name"]
                    try:
                        pvs_out = self._run(
                            ["pvs", "--noheadings", "-o", "vg_name", part_device],
                            capture=True,
                            check=False,
                        )
                        if pvs_out.returncode == 0 and pvs_out.stdout.strip():
                            vg_name = pvs_out.stdout.strip()
                            self._log(
                                _("Deaktiviere LVM Volume Group '{}' von Partition {}...").format(vg_name, part_device)
                            )
                            self._run(["vgchange", "-an", vg_name], check=False)
                    except Exception as e:
                        self._log(_("LVM-Check für {}: {}").format(part_device, e))

                try:
                    self._run(["vgchange", "-an"], check=False)
                    self._log(_("Alle LVM Volume Groups deaktiviert"))
                except Exception as e:
                    self._log(_("Generelle LVM-Deaktivierung: {}").format(e))

            except Exception as e:
                self._log(_("LVM-Deaktivierung fehlgeschlagen: {}").format(e))

        except Exception as e:
            self._log(_("Warnung beim Versuch, {} aufzuräumen: {}").format(device, e))

    def _apply_auto_partitioning_erase(self, plan: Dict[str, Any]):
        device = plan.get("device")
        layout = plan.get("auto_layout", {})
        if not device or not layout:
            raise ValueError(_("Plan für automatische Partitionierung unvollständig."))

        self._unmount_all_on_device(device)
        self._log(_("!!! DESTRUKTIVE AKTION: LÖSCHE ALLE DATEN AUF {} !!!").format(device))
        self._log(_("Deaktiviere alle Swap-Partitionen..."))
        self._run(["swapoff", "-a"], check=False)
        self._log(_("Hänge alle relevanten Dateisysteme aus..."))
        self._run(
            ["umount", "-a", "--types", "ext4,ext3,ext2,xfs,btrfs,vfat,ntfs"],
            check=False,
        )
        time.sleep(1)

        try:
            self._run(["wipefs", "-a", "-f", device])
        except subprocess.CalledProcessError:
            self._log(_("wipefs fehlgeschlagen, versuche alternative Methode..."))
            self._run(
                ["dd", "if=/dev/zero", f"of={device}", "bs=512", "count=1024"],
                check=False,
            )
        self._run(
            ["parted", "-s", device, "mklabel", "gpt" if plan.get("uefi") else "msdos"]
        )

        assignments = {}
        part_num = 1
        start_pos = "1MiB"
        if plan.get("uefi"):
            esp_size = layout.get("esp_size_mib", 512)
            end_pos = f"{esp_size + 1}MiB"
            self._run(
                [
                    "parted",
                    "-s",
                    device,
                    "mkpart",
                    "primary",
                    "fat32",
                    start_pos,
                    end_pos,
                ]
            )
            self._run(["parted", "-s", device, "set", str(part_num), "esp", "on"])
            assignments[self._get_partition_device(device, part_num)] = {
                "mountpoint": "/boot/efi",
                "format": True,
                "format_fs": "vfat",
            }
            part_num += 1
            start_pos = end_pos
        if layout.get("use_swap_partition"):
            swap_size = layout.get("swap_partition_gib", 8)
            swap_size_mib = swap_size * 1024
            if plan.get("uefi"):
                esp_size = layout.get("esp_size_mib", 512)
                end_pos = f"{esp_size + 1 + swap_size_mib}MiB"
            else:
                end_pos = f"{1 + swap_size_mib}MiB"
            self._run(
                [
                    "parted",
                    "-s",
                    device,
                    "mkpart",
                    "primary",
                    "linux-swap",
                    start_pos,
                    end_pos,
                ]
            )
            assignments[self._get_partition_device(device, part_num)] = {
                "mountpoint": "[SWAP]",
                "format": True,
                "format_fs": "swap",
            }
            part_num += 1
            start_pos = end_pos
        fs_type = layout.get("filesystem", "btrfs")
        if layout.get("use_separate_home"):
            home_percent = layout.get("home_size_percent", 50)
            root_end_pos = f"{100 - home_percent}%"
            self._run(
                [
                    "parted",
                    "-s",
                    device,
                    "mkpart",
                    "primary",
                    fs_type,
                    start_pos,
                    root_end_pos,
                ]
            )
            assignments[self._get_partition_device(device, part_num)] = {
                "mountpoint": "/",
                "format": True,
                "format_fs": fs_type,
            }
            part_num += 1
            self._run(
                [
                    "parted",
                    "-s",
                    device,
                    "mkpart",
                    "primary",
                    fs_type,
                    root_end_pos,
                    "100%",
                ]
            )
            assignments[self._get_partition_device(device, part_num)] = {
                "mountpoint": "/home",
                "format": True,
                "format_fs": fs_type,
            }
        else:
            self._run(
                [
                    "parted",
                    "-s",
                    device,
                    "mkpart",
                    "primary",
                    fs_type,
                    start_pos,
                    "100%",
                ]
            )
            assignments[self._get_partition_device(device, part_num)] = {
                "mountpoint": "/",
                "format": True,
                "format_fs": fs_type,
            }
        self._run(["partprobe", device], check=False)

        self._log(_("Formatiere Partitionen..."))
        for dev, assign in assignments.items():
            if assign.get("format"):
                fs_type = assign.get("format_fs", "ext4")
                self._make_fs(dev, fs_type)

        fs_type = layout.get("filesystem", "btrfs")
        if fs_type == "btrfs":
            root_dev = next(
                (d for d, a in assignments.items() if a.get("mountpoint") == "/"), None
            )
            if root_dev:
                self._create_btrfs_subvolumes(root_dev, plan)

        self._log(
            _("Automatische Partitionierung abgeschlossen. {} Partitionen erstellt.").format(len(assignments))
        )
        return assignments

    def _get_next_partition_number(self, device: str) -> int:
        try:
            out = self._run(["lsblk", "-Jpno", "NAME,TYPE", device], capture=True).stdout
            data = json.loads(out)
            part_count = 0
            def walk(node):
                nonlocal part_count
                if node.get("type") == "part":
                    part_count += 1
                for c in node.get("children", []) or []:
                    walk(c)
            for bd in data.get("blockdevices", []):
                walk(bd)
            return part_count + 1
        except Exception:
            return 1

    def _parted_free_segments_mib(self, device: str):
        self._log(f"Suche freien Speicher auf {device}...")
        
        # Schritt 1: Versuche GPT-Header Fehler zu reparieren (häufig bei VMs)
        # 'sgdisk -e' verschiebt die Backup-Header an das tatsächliche Ende der Platte
        try:
            subprocess.run(["sgdisk", "-e", device], capture_output=True)
        except:
            pass

        try:
            # Wir nutzen 'unit B' für absolute Präzision
            out = subprocess.check_output(
                ["parted", "-s", "-m", device, "unit", "B", "print", "free"],
                text=True,
                stderr=subprocess.STDOUT # Wir fangen auch Fehlermeldungen ab
            )
        except subprocess.CalledProcessError as e:
            self._log(f"Parted konnte {device} nicht lesen: {e.output}")
            return []

        segs = []
        for line in out.splitlines():
            line = line.strip()
            if not line or line.startswith("BYT;") or line.startswith(device) or line.startswith("/dev/"):
                continue
            
            parts = line.split(":")
            if len(parts) < 5:
                continue
            
            # Wir säubern ALLES in Feld 4 (muss 'free' enthalten)
            status = parts[4].lower()
            
            if "free" in status:
                try:
                    # Extrahiere nur die Zahlen aus Start, Ende, Größe
                    def get_num(s):
                        return int(re.sub(r"[^0-9]", "", s))

                    start_b = get_num(parts[1])
                    end_b   = get_num(parts[2])
                    size_b  = get_num(parts[3])
                    
                    size_mib = size_b / (1024 * 1024)
                    
                    if size_mib > 10.0: # Nur Bereiche über 10 MiB zählen
                        self._log(f"Freier Bereich gefunden: {size_mib:.2f} MiB")
                        segs.append((start_b / (1024*1024), end_b / (1024*1024), size_mib))
                except Exception as ex:
                    self._log(f"Fehler beim Parsen der Zeile: {ex}")
                    continue
        
        return segs

    def _find_existing_esp_partition(self, device: str) -> Optional[str]:
        try:
            out = subprocess.check_output(
                ["parted", "-m", device, "unit", "MiB", "print"],
                text=True,
                stderr=subprocess.DEVNULL,
            )
        except Exception:
            return None

        for line in out.splitlines():
            line = line.strip()
            if not line or line.startswith("BYT;") or line.startswith(device):
                continue
            parts = line.split(":")
            if len(parts) < 7:
                continue
            nr = parts[0].strip()
            flags = parts[6].strip() if len(parts) >= 7 else ""
            fs = parts[5].strip() if len(parts) >= 6 else ""
            if "esp" in flags or "boot" in flags and fs in ("fat32", "fat16", "fat"):
                if nr.isdigit():
                    return self._get_partition_device(device, int(nr))
        return None

    def _apply_existing_partition(self, plan: Dict[str, Any]):
        device = plan.get("device")
        part = plan.get("existing_partition")
        if not device or not part:
            raise ValueError(_("Vorhandene Partition wurde nicht ausgewählt."))

        self._unmount_all_on_device(device)

        fs_type = (plan.get("auto_layout") or {}).get("filesystem", "ext4")
        format_existing = bool(plan.get("format_existing", True))

        assignments = {
            part: {"mountpoint": "/", "format": format_existing, "format_fs": fs_type}
        }

        if plan.get("uefi"):
            esp = self._find_existing_esp_partition(device)
            if not esp:
                raise Exception(
                    _("UEFI-System erkannt, aber keine EFI-Systempartition (ESP) gefunden. "
                    "Bitte wähle ein Laufwerk mit vorhandener ESP oder nutze den Modus 'freien Platz verwenden'.")
                )
            assignments[esp] = {
                "mountpoint": "/boot/efi",
                "format": False,
                "format_fs": "vfat",
            }

        self._log(_("Formatiere Partitionen..."))
        for dev, assign in assignments.items():
            if assign.get("format"):
                self._make_fs(dev, assign.get("format_fs", "ext4"))

        if fs_type == "btrfs":
            self._create_btrfs_subvolumes(part, plan)

        self._log(_("Partition-Auswahlmodus abgeschlossen."))
        return assignments

    def _apply_auto_partitioning_free(self, plan: Dict[str, Any]):
        device = plan.get("device")
        if not device:
            raise ValueError(_("Kein Zielgerät angegeben."))

        layout = plan.get("auto_layout", {}) or {}
        fs_type = layout.get("filesystem", "ext4")
        root_size_gib = ((plan.get("free_space") or {}).get("root_size_gib")) or 30

        self._unmount_all_on_device(device)

        segs = self._parted_free_segments_mib(device)
        if not segs:
            raise Exception(_("Kein freier (nicht zugewiesener) Speicherbereich gefunden."))

        start_mib, end_mib, size_mib = max(segs, key=lambda t: t[2])

        required_mib = float(root_size_gib) * 1024.0
        esp_mib = float(layout.get("esp_size_mib", 512))
        swap_mib = float(layout.get("swap_partition_gib", 8)) * 1024.0 if layout.get("use_swap_partition") else 0.0

        esp_existing = self._find_existing_esp_partition(device) if plan.get("uefi") else None
        esp_needed = (plan.get("uefi") and not esp_existing)

        extra_mib = (esp_mib if esp_needed else 0.0) + swap_mib
        if required_mib + extra_mib > size_mib:
            raise Exception(
                _("Zu wenig freier Speicher: benötigt ca. {:.1f} GiB, vorhanden {:.1f} GiB.").format(
                    (required_mib + extra_mib)/1024.0, size_mib/1024.0
                )
            )

        part_num = self._get_next_partition_number(device)
        assignments: Dict[str, Any] = {}
        cur = start_mib

        if esp_needed:
            esp_start = f"{cur}MiB"
            esp_end = f"{cur + esp_mib}MiB"
            self._log(_("Erstelle ESP im freien Bereich: {} - {}").format(esp_start, esp_end))
            self._run(
                ["parted", "-s", device, "mkpart", "primary", "fat32", esp_start, esp_end]
            )
            self._run(["parted", "-s", device, "set", str(part_num), "esp", "on"])
            esp_dev = self._get_partition_device(device, part_num)
            assignments[esp_dev] = {
                "mountpoint": "/boot/efi",
                "format": True,
                "format_fs": "vfat",
            }
            part_num += 1
            cur += esp_mib
        elif esp_existing:
            assignments[esp_existing] = {
                "mountpoint": "/boot/efi",
                "format": False,
                "format_fs": "vfat",
            }

        if layout.get("use_swap_partition"):
            swap_start = f"{cur}MiB"
            swap_end = f"{cur + swap_mib}MiB"
            self._log(_("Erstelle Swap im freien Bereich: {} - {}").format(swap_start, swap_end))
            self._run(
                ["parted", "-s", device, "mkpart", "primary", "linux-swap", swap_start, swap_end]
            )
            swap_dev = self._get_partition_device(device, part_num)
            assignments[swap_dev] = {
                "mountpoint": "[SWAP]",
                "format": True,
                "format_fs": "swap",
            }
            part_num += 1
            cur += swap_mib

        root_start = f"{cur}MiB"
        root_end = f"{cur + required_mib}MiB"
        self._log(_("Erstelle Root im freien Bereich: {} - {}").format(root_start, root_end))
        self._run(
            ["parted", "-s", device, "mkpart", "primary", fs_type, root_start, root_end]
        )
        root_dev = self._get_partition_device(device, part_num)
        assignments[root_dev] = {"mountpoint": "/", "format": True, "format_fs": fs_type}
        part_num += 1
        cur += required_mib

        remaining_mib = max(0.0, end_mib - cur)
        if layout.get("use_separate_home") and fs_type != "btrfs" and remaining_mib > 1024.0:
            home_start = f"{cur}MiB"
            home_end = f"{end_mib}MiB"
            self._log(_("Erstelle /home im freien Bereich: {} - {}").format(home_start, home_end))
            self._run(
                ["parted", "-s", device, "mkpart", "primary", fs_type, home_start, home_end]
            )
            home_dev = self._get_partition_device(device, part_num)
            assignments[home_dev] = {
                "mountpoint": "/home",
                "format": True,
                "format_fs": fs_type,
            }

        self._run(["partprobe", device], check=False)

        self._log(_("Formatiere Partitionen..."))
        for dev, assign in assignments.items():
            if assign.get("format"):
                self._make_fs(dev, assign.get("format_fs", "ext4"))

        if fs_type == "btrfs":
            self._create_btrfs_subvolumes(root_dev, plan)

        self._log(_("Installation im freien Bereich vorbereitet. {} Partition(en) konfiguriert.").format(len(assignments)))
        return assignments

    def _get_uuid(self, device: str) -> str:
        try:
            res = self._run(
                ["blkid", "-s", "UUID", "-o", "value", device], capture=True
            )
            return res.stdout.strip()
        except subprocess.CalledProcessError:
            raise Exception(_("Konnte UUID für Gerät {} nicht ermitteln.").format(device))

    def _make_fs(self, device: str, fs_type: str):
        self._log(_("Formatiere {} als {}...").format(device, fs_type))
        if fs_type == "vfat":
            if self._is_mounted(device):
                self._log(_("Gerät {} ist gemountet, unmounte es...").format(device))
                self._run(["umount", "-f", "-l", device], check=False)
                time.sleep(1)
            self._run(["mkfs.vfat", "-I", "-F32", device])
        elif fs_type == "btrfs":
            self._run(["mkfs.btrfs", "-f", device])
        elif fs_type == "ext4":
            self._run(["mkfs.ext4", "-F", device])
        elif fs_type == "xfs":
            self._run(["mkfs.xfs", "-f", device])
        elif fs_type == "swap":
            self._run(["mkswap", device])
        else:
            raise ValueError(_("Unbekanntes Dateisystem: {}").format(fs_type))

    def _create_btrfs_subvolumes(self, device: str, plan: Dict[str, Any]):
        subvolumes = plan.get("auto_layout", {}).get("subvolumes", [])
        if not subvolumes:
            self._log(_("Keine Btrfs-Subvolumes konfiguriert."))
            return

        subvol_mapping = {
            "@": "/",
            "@home": "/home",
            "@snapshots": "/.snapshots",
            "@var_log": "/var/log",
            "@var_cache": "/var/cache",
            "@var_tmp": "/var/tmp",
            "@tmp": "/tmp",
            "@opt": "/opt",
            "@srv": "/srv",
            "@var_lib_libvirt_images": "/var/lib/libvirt/images",
        }

        self._log(_("Erstelle Btrfs-Subvolumes auf {}...").format(device))
        temp_mount = "/tmp/btrfs_temp"
        os.makedirs(temp_mount, exist_ok=True)
        try:
            self._run(["mount", device, temp_mount])
            for subvol_name in subvolumes:
                subvol_path = os.path.join(temp_mount, subvol_name)
                self._log(_("Erstelle Subvolume: {}").format(subvol_name))
                self._run(["btrfs", "subvolume", "create", subvol_path])
                mount_point = subvol_mapping.get(subvol_name)
                if mount_point:
                    if "subvol_mounts" not in plan:
                        plan["subvol_mounts"] = {}
                    plan["subvol_mounts"][mount_point] = {
                        "device": device,
                        "subvol": subvol_name,
                    }
            self._log(_("✓ Btrfs-Subvolumes erstellt"))
        finally:
            self._run(["umount", temp_mount], check=False)
            try:
                os.rmdir(temp_mount)
            except:
                pass

    def _mount_filesystems(self, plan):
        assignments = plan.get("manual_partitions", {})
        if not assignments:
            mode = plan.get("mode", "unknown")
            raise Exception(
                _("Keine Partitionen zum Einhängen gefunden. Modus: {}. Möglicherweise wurde die Partitionierung nicht korrekt abgeschlossen.").format(mode)
            )
        root_part, home_part, esp_part, swap_part = None, None, None, None
        for dev, assign in assignments.items():
            mp = assign.get("mountpoint")
            if mp == "/":
                root_part = dev
            elif mp == "/home":
                home_part = dev
            elif mp == "/boot/efi":
                esp_part = dev
            elif mp == "[SWAP]":
                swap_part = dev
        if not root_part:
            raise Exception(_("Keine Wurzelpartition (/) zugewiesen."))

        time.sleep(3)

        if not self._verify_filesystem(root_part):
            self._log(
                _("Warnung: Root-Partition-Dateisystem nicht vollständig verifiziert")
            )

        subvol_mounts = plan.get("subvol_mounts", {})
        root_fs = assignments[root_part].get("format_fs", "ext4")

        if root_fs == "btrfs" and subvol_mounts:
            self._log(_("Hänge Btrfs-Subvolumes ein..."))
            root_subvol = subvol_mounts.get("/", {})
            if root_subvol:
                self._run(
                    [
                        "mount",
                        "-o",
                        f"subvol={root_subvol['subvol']}",
                        root_part,
                        self.target_root,
                    ]
                )
                self._log(_("✓ Root-Subvolume {} als / eingehängt").format(root_subvol['subvol']))
            else:
                self._mount_partition_safe(root_part, self.target_root, "Root")

            for mount_point, mount_info in subvol_mounts.items():
                if mount_point == "/":
                    continue
                target_path = os.path.join(self.target_root, mount_point.lstrip("/"))
                os.makedirs(target_path, exist_ok=True)
                self._run(
                    [
                        "mount",
                        "-o",
                        f"subvol={mount_info['subvol']}",
                        mount_info["device"],
                        target_path,
                    ]
                )
                self._log(
                    _("✓ Subvolume {} als {} eingehängt").format(mount_info['subvol'], mount_point)
                )

            if home_part and "/home" not in subvol_mounts:
                home_mp = os.path.join(self.target_root, "home")
                os.makedirs(home_mp, exist_ok=True)
                if not self._verify_filesystem(home_part):
                    self._log(
                        _("Warnung: Home-Partition-Dateisystem nicht vollständig verifiziert")
                    )
                self._mount_partition_safe(home_part, home_mp, "Home")
        else:
            self._mount_partition_safe(root_part, self.target_root, "Root")
            if home_part:
                home_mp = os.path.join(self.target_root, "home")
                os.makedirs(home_mp, exist_ok=True)
                if not self._verify_filesystem(home_part):
                    self._log(
                        _("Warnung: Home-Partition-Dateisystem nicht vollständig verifiziert")
                    )
                self._mount_partition_safe(home_part, home_mp, "Home")

        if esp_part and plan.get("uefi"):
            esp_mp = os.path.join(self.target_root, "boot/efi")
            os.makedirs(esp_mp, exist_ok=True)
            if not self._verify_filesystem(esp_part, "vfat"):
                self._log(_("Warnung: ESP-Partition nicht als vfat verifiziert"))
            self._mount_partition_safe(esp_part, esp_mp, "EFI System")
        if swap_part:
            self._run(["swapon", swap_part])

    def _generate_fstab(self, plan):
        self._log(_("Generiere /etc/fstab..."))
        assignments = plan.get("manual_partitions", {})
        etc_dir = os.path.join(self.target_root, "etc")
        os.makedirs(etc_dir, exist_ok=True)

        with open(os.path.join(self.target_root, "etc/fstab"), "w") as f:
            f.write(
                "# /etc/fstab: static file system information.\n# <file system> <mount point> <type> <options> <dump> <pass>\n"
            )
            subvol_mounts = plan.get("subvol_mounts", {})
            root_dev = None
            root_fs = None
            for dev, assign in assignments.items():
                if assign.get("mountpoint") == "/":
                    root_dev = dev
                    root_fs = assign.get("format_fs", "ext4")
                    break

            if root_fs == "btrfs" and subvol_mounts:
                for mount_point, mount_info in subvol_mounts.items():
                    uuid = self._get_uuid(mount_info["device"])
                    options = (
                        f"defaults,noatime,compress=zstd,subvol={mount_info['subvol']}"
                    )
                    dump_pass = "0 1" if mount_point == "/" else "0 2"
                    f.write(
                        f"UUID={uuid}\t{mount_point}\tbtrfs\t{options}\t{dump_pass}\n"
                    )
            else:
                for dev, assign in assignments.items():
                    if assign.get("mountpoint") == "/":
                        uuid = self._get_uuid(dev)
                        fs_type = assign.get("format_fs", "ext4")
                        options = (
                            "defaults,noatime,compress=zstd"
                            if fs_type == "btrfs"
                            else "defaults,noatime"
                        )
                        f.write(f"UUID={uuid}\t/\t{fs_type}\t{options}\t0 1\n")
                        break
                for dev, assign in assignments.items():
                    mp = assign.get("mountpoint")
                    if mp in ["/home", "/boot/efi"]:
                        uuid = self._get_uuid(dev)
                        fs_type = (
                            "vfat"
                            if mp == "/boot/efi"
                            else assign.get("format_fs", "ext4")
                        )
                        options = (
                            "defaults,noatime" if fs_type != "vfat" else "defaults"
                        )
                        f.write(f"UUID={uuid}\t{mp}\t{fs_type}\t{options}\t0 2\n")
                    elif mp == "[SWAP]":
                        uuid = self._get_uuid(dev)
                        f.write(f"UUID={uuid}\tnone\tswap\tsw\t0 0\n")
        self._log(_("fstab wurde geschrieben."))

    def _configure_mirror(self, plan):
        self._log(_("Konfiguriere Paket-Mirrors..."))
        mirror_url = plan.get("mirror_url", "")
        if not mirror_url or mirror_url == "https://repo-default.voidlinux.org":
            self._log(_("Verwende Standard-Mirror-Konfiguration"))
            return
        try:
            self._log(_("Konfiguriere Mirror: {}").format(mirror_url))
            xbps_dir = os.path.join(self.target_root, "etc/xbps.d")
            os.makedirs(xbps_dir, exist_ok=True)
            with open(os.path.join(xbps_dir, "00-repository-main.conf"), "w") as f:
                f.write(f"repository={mirror_url}/current\n")
            with open(os.path.join(xbps_dir, "10-repository-multilib.conf"), "w") as f:
                f.write(f"repository={mirror_url}/current/multilib\n")
                f.write(f"repository={mirror_url}/current/multilib/nonfree\n")
            with open(os.path.join(xbps_dir, "20-repository-nonfree.conf"), "w") as f:
                f.write(f"repository={mirror_url}/current/nonfree\n")
            self._log(_("✓ Mirror konfiguriert: {}").format(mirror_url))
        except Exception as e:
            self._log(_("⚠ Fehler bei Mirror-Konfiguration: {}").format(e))
            self._log(_("Verwende Standard-Mirror als Fallback"))

    def _setup_cosmic_repo(self):
        """
        Richtet das Cosmic-Repository von Bella109 ein.
        """
        self._log(_("Richte Cosmic-Repository ein (bellawagner.de)..."))
        try:
            repo_conf_dir = os.path.join(self.target_root, "etc/xbps.d")
            os.makedirs(repo_conf_dir, exist_ok=True)
            
            conf_file = os.path.join(repo_conf_dir, "59-cosmic.conf")
            
            with open(conf_file, "w") as f:
                f.write("repository=https://bellawagner.de/repo/x86_64\n")
            
            self._log(_("✓ Cosmic-Repository hinzugefügt."))
        except Exception as e:
            self._log(_("Fehler beim Hinzufügen des Cosmic-Repos: {}").format(e))
            raise

    def _setup_xbps_config(self):
        self._log(_("Setting up XBPS configuration..."))
        if not os.path.exists(self.target_root):
            raise Exception(f"Target root {self.target_root} does not exist")

        xbps_cache_dir = os.path.join(self.target_root, "var/cache/xbps")
        xbps_db_dir = os.path.join(self.target_root, "var/db/xbps")
        xbps_keys_dir = os.path.join(self.target_root, "var/db/xbps/keys")
        usr_share_xbps_d = os.path.join(self.target_root, "usr/share/xbps.d")
        etc_xbps_d = os.path.join(self.target_root, "etc/xbps.d")

        for dir_path in [
            xbps_cache_dir,
            xbps_db_dir,
            xbps_keys_dir,
            usr_share_xbps_d,
            etc_xbps_d,
        ]:
            os.makedirs(dir_path, exist_ok=True)
            self._log(_("Created directory: {}").format(dir_path))

        host_keys_dir = "/var/db/xbps/keys"
        if os.path.exists(host_keys_dir):
            for key_file in os.listdir(host_keys_dir):
                shutil.copy2(
                    os.path.join(host_keys_dir, key_file),
                    os.path.join(xbps_keys_dir, key_file),
                )
            self._log(_("Copied XBPS keys from {}").format(host_keys_dir))

        self._log(_("Testing network connectivity..."))
        try:
            urllib.request.urlopen("https://repo-default.voidlinux.org", timeout=10)
            self._log(_("Network connectivity: OK"))
        except Exception as net_err:
            self._log(_("Network connectivity test failed: {}").format(net_err))

    def _validate_packages(self, packages):
        self._log(_("Validating {} packages...").format(len(packages)))
        valid_packages = []
        invalid_packages = []

        for pkg in packages:
            essential_packages = {
                "base-system", "grub", "grub-x86_64-efi", "bash", "curl", "git", "NetworkManager",
            }
            if pkg in essential_packages:
                valid_packages.append(pkg)
                continue

            try:
                # Wir prüfen erst, ob das Paket existiert (kann auch in nonfree sein, 
                # wenn das repo im target schon installiert ist)
                result = subprocess.run(
                    ["xbps-query", "-r", self.target_root, "-R", pkg], 
                    capture_output=True, text=True
                )
                if result.returncode == 0:
                    valid_packages.append(pkg)
                else:
                    # Fallback: Versuche es im Host zu finden, falls Target-Sync noch nicht durch ist
                    result_host = subprocess.run(
                        ["xbps-query", "-R", pkg], capture_output=True, text=True
                    )
                    if result_host.returncode == 0:
                        valid_packages.append(pkg)
                    else:
                        self._log(_("Package '{}' not found in repositories").format(pkg))
                        invalid_packages.append(pkg)
            except Exception as e:
                self._log(_("Error checking package '{}': {}, skipping").format(pkg, e))
                invalid_packages.append(pkg)

        self._log(
            _("Validation complete: {} valid, {} invalid").format(len(valid_packages), len(invalid_packages))
        )
        if invalid_packages:
            self._log(
                _("Invalid packages skipped: {}{}".format(invalid_packages[:10], '...' if len(invalid_packages) > 10 else ''))
            )
        return valid_packages

    def _is_root_filesystem_btrfs(self, plan):
        try:
            manual_partitions = plan.get("manual_partitions", {})
            for device, assignment in manual_partitions.items():
                if assignment.get("mountpoint") == "/":
                    fs_type = assignment.get("format_fs", "")
                    return fs_type == "btrfs"
            auto_layout = plan.get("auto_layout", {})
            if auto_layout:
                fs_type = auto_layout.get("filesystem", "")
                return fs_type == "btrfs"
            return False
        except Exception as e:
            self._log(_("Error determining root filesystem type: {}").format(e))
            return False

    def _xbps_install(self, plan):
        # Determine installation mode: 'net' (download) or 'live' (clone)
        install_mode = plan.get("install_source", "net")
        self._log(_("Installations-Modus: {}").format(install_mode))

        # --- GEMEINSAME VORBEREITUNGEN ---
        
        # 1. XBPS Setup (Keys kopieren etc.)
        try:
            self._setup_xbps_config()
        except Exception as setup_err:
            self._log(_("XBPS setup failed: {}").format(setup_err))
            raise

        # 2. NEU: Cosmic Repo einrichten (Muss VOR dem Sync passieren!)
        software_selection = plan.get("software", [])
        if "cosmic-desktop-full" in software_selection:
            self._setup_cosmic_repo()

        # 3. Paketlisten vorbereiten
        all_pkgs = ["base-system", "grub"] + software_selection
        if plan.get("uefi"):
            all_pkgs.append("grub-x86_64-efi")
        
            all_pkgs.extend(["grub-btrfs", "grub-btrfs-runit"])

        try:
            with open("/proc/cpuinfo", "r") as f:
                cpu_info = f.read()
                if "GenuineIntel" in cpu_info:
                    all_pkgs.append("intel-ucode")
                elif "AuthenticAMD" in cpu_info:
                    all_pkgs.append("linux-firmware-amd")
        except:
            pass

        all_pkgs = sorted(list(set(all_pkgs)))
        
        repo_pkgs = [p for p in all_pkgs if "void-repo" in p]
        software_pkgs = [p for p in all_pkgs if "void-repo" not in p]

        # --- LOGIK WEICHE ---

        if install_mode == "live":
            # --- MODUS: LIVE SYSTEM KLONEN ---
            self._log(_("Starte Live-System-Klon (Offline-Installation)..."))
            
            # 1. Dateisystem kopieren
            self._copy_iso_customizations() 
            
            # 2. Zusätzliche Pakete nachinstallieren (falls Internet vorhanden)
            try:
                self._setup_xbps_config()
                self._log(_("Synchronisiere Repositories für Nachinstallation..."))
                subprocess.run(["xbps-install", "-S", "-r", self.target_root], capture_output=True)
                
                final_pkgs = self._validate_packages(software_pkgs)
                if final_pkgs:
                    self._log(_("Installiere zusätzliche/aktualisierte Pakete..."))
                    process = subprocess.Popen(
                        ["xbps-install", "-uy", "-r", self.target_root] + final_pkgs,
                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, universal_newlines=True
                    )
                    while True:
                        output = process.stdout.readline()
                        if output == "" and process.poll() is not None: break
                        if output: self._log(output.strip())
            except Exception as e:
                self._log(_("Warnung: Online-Nachinstallation fehlgeschlagen (nicht kritisch bei Live-Klon): {}").format(e))

        else:
            # --- MODUS: NETINSTALL (DOWNLOAD) ---
            self._log(_("Starte Netinstall (Online-Installation)..."))

            # 1. Sync
            self._log(_("Synchronizing host repositories..."))
            subprocess.run(["xbps-install", "-S"], text=True, capture_output=True)
            self._log(_("Synchronizing target repositories..."))
            subprocess.run(["xbps-install", "-S", "-r", self.target_root], text=True, capture_output=True)

            # 2. Phase 1: Repositories installieren
            if repo_pkgs:
                self._log(_("Installing repositories: {}").format(repo_pkgs))
                try:
                    repo_process = subprocess.Popen(
                        ["xbps-install", "-uy", "-r", self.target_root] + repo_pkgs,
                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
                    )
                    while True:
                        output = repo_process.stdout.readline()
                        if output == "" and repo_process.poll() is not None: break
                        if output: self._log(output.strip())
                    
                    if repo_process.poll() == 0:
                        self._log(_("Repositories installed. Syncing again..."))
                        subprocess.run(["xbps-install", "-S", "-r", self.target_root], capture_output=True)
                except Exception as e:
                    self._log(f"Repo install error: {e}")

            # 3. Phase 2: Restliche Software installieren
            final_pkgs = self._validate_packages(software_pkgs)
            if not final_pkgs:
                self._log(_("No valid packages to install!"))
                final_pkgs = ["base-system", "grub"]

            self._log(_("Installing {} validated packages...").format(len(final_pkgs)))
            try:
                process = subprocess.Popen(
                    ["xbps-install", "-uy", "-r", self.target_root] + final_pkgs,
                    stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, universal_newlines=True
                )
                while True:
                    output = process.stdout.readline()
                    if output == "" and process.poll() is not None: break
                    if output: self._log(output.strip())

                if process.poll() != 0:
                    raise subprocess.CalledProcessError(process.poll(), process.args)
                self._log(_("Package installation: OK"))

            except subprocess.CalledProcessError as e:
                self._log(_("Package installation failed with exit code {}").format(e.returncode))
                raise

    def _configure_hostname(self, plan):
        hostname = plan.get("user", {}).get("hostname")
        if hostname:
            with open(os.path.join(self.target_root, "etc/hostname"), "w") as f:
                f.write(hostname + "\n")

    def _configure_locale_kbd(self, plan):
        self._log(_("Konfiguriere Sprache und Tastatur..."))
        language = plan.get("language", "de_DE.UTF-8")
        try:
            locales_file = os.path.join(self.target_root, "etc/default/libc-locales")
            if os.path.exists(locales_file):
                with open(locales_file, "r") as f:
                    content = f.read()
                pattern = r"#?\b" + re.escape(language) + r"\b"
                content = re.sub(pattern, language, content)
                with open(locales_file, "w") as f:
                    f.write(content)
                self._enter_chroot_mounts()
                try:
                    self._run(["xbps-reconfigure", "-f", "glibc-locales"], chroot=True)
                finally:
                    self._leave_chroot_mounts()
        except Exception as e:
            self._log(_("Warnung: Konnte Locale nicht generieren: {}").format(e))

        try:
            with open(os.path.join(self.target_root, "etc/locale.conf"), "w") as f:
                f.write(f"LANG={language}\n")
        except Exception as e:
            self._log(_("Warnung: Konnte Sprache nicht setzen: {}").format(e))

        keyboard = plan.get("keyboard")
        console_keyboard_map = {
            "de": "de", "de-nodeadkeys": "de-latin1-nodeadkeys", "fr": "fr",
            "es": "es", "it": "it", "gb": "gb", "us": "us",
        }
        console_keyboard = console_keyboard_map.get(keyboard, keyboard)
        rc_conf_path = os.path.join(self.target_root, "etc/rc.conf")
        try:
            if os.path.exists(rc_conf_path):
                with open(rc_conf_path, "r") as f:
                    lines = f.readlines()
            else:
                lines = []
            keymap_found = False
            with open(rc_conf_path, "w") as f:
                for line in lines:
                    if line.strip().startswith("KEYMAP="):
                        f.write(f"KEYMAP={console_keyboard}\n")
                        keymap_found = True
                    else:
                        f.write(line)
                if not keymap_found:
                    f.write(f"KEYMAP={console_keyboard}\n")
        except Exception as e:
            self._log(_("Warnung: Konnte rc.conf nicht aktualisieren: {}").format(e))

        if keyboard:
            self._configure_x11_keyboard(keyboard)

    def _configure_x11_keyboard(self, keyboard):
        try:
            x11_config_dir = os.path.join(self.target_root, "etc/X11/xorg.conf.d")
            os.makedirs(x11_config_dir, exist_ok=True)
            layout_parts = keyboard.split("-", 1)
            layout = layout_parts[0]
            variant = layout_parts[1] if len(layout_parts) > 1 else None
            
            config_content = f"""Section "InputClass"
        Identifier "system-keyboard"
        MatchIsKeyboard "on"
        Option "XkbLayout" "{layout}"
        Option "XkbModel" "pc105"
"""
            if variant:
                config_content += f'        Option "XkbVariant" "{variant}"\n'
            config_content += "EndSection\n"

            with open(os.path.join(x11_config_dir, "00-keyboard.conf"), "w") as f:
                f.write(config_content)
        except Exception as e:
            self._log(_("⚠ Warnung: X11-Tastaturlayout-Konfiguration fehlgeschlagen: {}").format(e))

    def _configure_timezone(self, plan):
        self._log(_("Konfiguriere Zeitzone..."))
        timezone = plan.get("timezone", "UTC") or "UTC"
        source_tz_path = f"/usr/share/zoneinfo/{timezone}"
        target_localtime_path = os.path.join(self.target_root, "etc/localtime")
        
        if not os.path.exists(os.path.join(self.target_root, source_tz_path.lstrip("/"))):
             source_tz_path = "/usr/share/zoneinfo/UTC"

        try:
            self._run(["ln", "-sf", source_tz_path, target_localtime_path])
        except Exception as e:
            self._log(_("Fehler beim Setzen der Zeitzone: {}").format(e))

    def _configure_user(self, plan):
        user_config = plan.get("user", {})
        if not user_config:
            return

        username = user_config.get("name")
        password = user_config.get("password")
        root_password = user_config.get("root_password", password)
        groups = user_config.get("groups", ["wheel"])

        self._log(_("Konfiguriere Benutzer: {}").format(username if username else _('nur root')))
        self._enter_chroot_mounts()
        try:
            if root_password:
                self._log(_("Setze root-Passwort..."))
                try:
                    process = subprocess.Popen(
                        ["chroot", self.target_root, "passwd", "root"],
                        stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
                    )
                    stdout, stderr = process.communicate(input=f"{root_password}\n{root_password}\n")
                    if process.returncode != 0:
                        self._log(_("⚠ Warnung: Konnte root-Passwort nicht setzen: {}").format(stderr))
                except Exception as e:
                    self._log(_("Fehler beim Setzen des root-Passworts: {}").format(e))

            if username:
                self._log(_("Erstelle Benutzer: {}").format(username))
                try:
                    for group in groups:
                        try:
                            res = self._run(["getent", "group", group], chroot=True, capture=True, check=False)
                            if res.returncode != 0:
                                self._run(["groupadd", group], chroot=True, check=False)
                        except: pass

                    self._run(
                        ["useradd", "-m", "-s", user_config.get("shell", "/bin/bash"), "-G", ",".join(groups), username],
                        chroot=True,
                    )
                    
                    if password:
                        process = subprocess.Popen(
                            ["chroot", self.target_root, "passwd", username],
                            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
                        )
                        stdout, stderr = process.communicate(input=f"{password}\n{password}\n")
                        if process.returncode != 0:
                            self._log(_("⚠ Warnung: Konnte Passwort für {} nicht setzen: {}").format(username, stderr))

                    # Sudo
                    sudoers_path = os.path.join(self.target_root, "etc/sudoers.d/wheel")
                    os.makedirs(os.path.dirname(sudoers_path), exist_ok=True)
                    with open(sudoers_path, "w") as f:
                        f.write("%wheel ALL=(ALL:ALL) ALL\n")
                    os.chmod(sudoers_path, 0o440)
                except Exception as e:
                    self._log(_("Fehler bei Benutzererstellung: {}").format(e))
        finally:
            self._leave_chroot_mounts()

    def _enable_services(self, plan):
        self._log(_("Aktiviere System-Services..."))
        self._enter_chroot_mounts()
        try:
            # Pfad zum runit default Verzeichnis im Zielsystem
            runit_default_dir = "/etc/runit/runsvdir/default"
            
            # 1. Kern-Dienste (Immer nötig)
            core_services = ["dbus", "elogind", "acpid", "cronie", "alsa"]
            
            # 2. Hardware & Features
            feature_services = ["avahi-daemon", "bluetoothd", "cupsd", "tlp", "smartd", "rtkit", "polkitd", "zramen"]
            
            # 3. Netzwerk-Logik (Kritisch für KDE)
            # Wir bevorzugen NetworkManager für Desktops
            has_network_manager = os.path.exists(os.path.join(self.target_root, "etc/sv/NetworkManager"))
            
            for service in core_services:
                self._enable_runit_service(service)

            for service in feature_services:
                self._enable_runit_service(service)

            if has_network_manager:
                self._log(_("✓ Aktiviere NetworkManager (Desktop-Modus)"))
                self._enable_runit_service("NetworkManager")
                # WICHTIG: dhcpcd deaktivieren, wenn NetworkManager genutzt wird, um Konflikte zu vermeiden
                self._disable_runit_service("dhcpcd")
                self._disable_runit_service("wpa_supplicant")
            else:
                # Fallback auf Standard-Ethernet falls kein NM vorhanden
                self._log(_("Info: NetworkManager nicht gefunden, verwende dhcpcd"))
                self._enable_runit_service("dhcpcd")

            # 4. Display Manager (Login-Bildschirm)
            desktop_services = ["sddm", "gdm", "lightdm", "lxdm"]
            for ds in desktop_services:
                if os.path.exists(os.path.join(self.target_root, f"etc/sv/{ds}")):
                    self._enable_runit_service(ds)
                    self._log(_("✓ Display Manager aktiviert: {}").format(ds))
                    break 
            
            self._log(_("Initialisiere Audio-Lautstärke..."))
            self._run(["amixer", "sset", "Master", "unmute"], chroot=True, check=False)
            self._run(["amixer", "sset", "Master", "100%"], chroot=True, check=False)

        finally:
            self._leave_chroot_mounts()
            
    def _enable_runit_service(self, service):
        """Erstellt einen Symlink in /etc/runit/runsvdir/default/, um einen Dienst zu aktivieren."""
        try:
            service_path = f"/etc/sv/{service}"
            full_service_path = os.path.join(self.target_root, service_path.lstrip("/"))

            # Prüfen, ob der Dienst überhaupt installiert ist
            if not os.path.exists(full_service_path):
                return False

            target_link = f"/etc/runit/runsvdir/default/{service}"
            full_target_path = os.path.join(self.target_root, target_link.lstrip("/"))

            # Zielverzeichnis sicherstellen
            os.makedirs(os.path.dirname(full_target_path), exist_ok=True)

            # Falls schon ein Link oder eine Datei da ist, entfernen
            if os.path.exists(full_target_path) or os.path.islink(full_target_path):
                self._run(["rm", "-f", target_link], chroot=True)

            # Den Symlink im chroot erstellen
            # Wir linken von /etc/sv/name nach /etc/runit/runsvdir/default/name
            self._run(["ln", "-s", service_path, target_link], chroot=True)
            return True
        except Exception as e:
            self._log(_("⚠ Fehler beim Aktivieren von Service {}: {}").format(service, e))
            return False

    def _disable_runit_service(self, service):
        """Entfernt einen runit-Service Symlink im Zielsystem."""
        try:
            target_link = f"/etc/runit/runsvdir/default/{service}"
            full_target_path = os.path.join(self.target_root, target_link.lstrip("/"))
            if os.path.exists(full_target_path) or os.path.islink(full_target_path):
                self._run(["rm", "-f", target_link], chroot=True)
                return True
        except:
            pass
        return False

    def _configure_pipewire(self, plan):
        if not os.path.exists(os.path.join(self.target_root, "usr/share/pipewire/pipewire.conf")):
            return

        self._log(_("Konfiguriere PipeWire (System & User Services)..."))
        self._enter_chroot_mounts()
        
        try:
            self._log(_("Entferne Legacy PulseAudio falls vorhanden..."))
            self._run(["xbps-remove", "-R", "-y", "pulseaudio"], chroot=True, check=False)

            etc_pw_dir = os.path.join(self.target_root, "etc/pipewire/pipewire.conf.d")
            os.makedirs(etc_pw_dir, exist_ok=True)

            target_wp = "/usr/share/examples/wireplumber/10-wireplumber.conf"
            link_wp = "/etc/pipewire/pipewire.conf.d/10-wireplumber.conf"
            self._run(["ln", "-sf", target_wp, link_wp], chroot=True)

            target_pulse = "/usr/share/examples/pipewire/20-pipewire-pulse.conf"
            link_pulse = "/etc/pipewire/pipewire.conf.d/20-pipewire-pulse.conf"
            self._run(["ln", "-sf", target_pulse, link_pulse], chroot=True)

            self._log(_("✓ Systemweite PipeWire-Konfiguration erstellt."))

            user_config = plan.get("user", {})
            username = user_config.get("name")
            
            if username:
                self._log(_("Richte User-Service für {} ein...").format(username))
                
                user_home = os.path.join(self.target_root, "home", username)
                user_runit_dir = os.path.join(user_home, ".config/runit/sv")
                
                os.makedirs(user_runit_dir, exist_ok=True)
                
                link_target = os.path.join(user_runit_dir, "pipewire").replace(self.target_root, "")
                
                self._run(["ln", "-sf", "/etc/sv/pipewire", link_target], chroot=True)
                self._run(["chown", "-R", f"{username}:{username}", f"/home/{username}/.config"], chroot=True)
                
                self._log(_("✓ User-Service für PipeWire eingerichtet."))

            os.makedirs(os.path.join(self.target_root, "etc/alsa/conf.d"), exist_ok=True)
            self._run(["ln", "-sf", "/usr/share/alsa/alsa.conf.d/50-pipewire.conf", "/etc/alsa/conf.d/"], chroot=True)
            self._run(["ln", "-sf", "/usr/share/alsa/alsa.conf.d/99-pipewire-default.conf", "/etc/alsa/conf.d/"], chroot=True)

        except Exception as e:
            self._log(_("Fehler bei der PipeWire Konfiguration: {}").format(e))
        finally:
            self._leave_chroot_mounts()

    def _configure_flatpak(self, plan):
        if "flatpak" not in plan.get("software", []):
            return
        self._log(_("Füge Flathub-Repository zu Flatpak hinzu..."))
        self._enter_chroot_mounts()
        try:
            self._run(["flatpak", "remote-add", "--if-not-exists", "flathub", "https://dl.flathub.org/repo/flathub.flatpakrepo"], chroot=True)
        finally:
            self._leave_chroot_mounts()

    def _install_bootloader(self, plan):
        self._log(_("Installiere GRUB Bootloader..."))
        self._enter_chroot_mounts()
        try:
            grub_default = os.path.join(self.target_root, "etc/default/grub")
            if os.path.exists(grub_default):
                with open(grub_default, "a") as f:
                    f.write("\nGRUB_DISABLE_OS_PROBER=false\n")

            if plan.get("uefi"):
                self._run(
                    ["grub-install", "--target=x86_64-efi", "--efi-directory=/boot/efi", "--bootloader-id=Void"],
                    chroot=True,
                )
            else:
                self._run(["grub-install", "--target=i386-pc", plan["device"]], chroot=True)
            
            self._run(["grub-mkconfig", "-o", "/boot/grub/grub.cfg"], chroot=True)
        finally:
            self._leave_chroot_mounts()

    def _copy_iso_customizations(self):
        try:
            self._log(_("Kopiere vollständige Live-Umgebung zum installierten System..."))
            tar_in = "--create --one-file-system --xattrs"
            cmd = f"tar {tar_in} -f - / 2>/dev/null | tar --extract --xattrs --xattrs-include='*' --preserve-permissions -f - -C {self.target_root}"
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
            if result.returncode == 0:
                self._log(_("✓ Vollständige Live-Umgebung erfolgreich kopiert"))
            else:
                raise Exception(_("Kopie der Live-Umgebung fehlgeschlagen: {}").format(result.stderr))
            self._cleanup_live_copy()
        except Exception as e:
            self._log(_("Fehler beim Kopieren der vollständigen Live-Umgebung: {}").format(e))
            raise

    def _cleanup_live_copy(self):
        try:
            self._log(_("Bereinige temporäre Daten nach Live-Umgebungskopie..."))
            subprocess.run(["chroot", self.target_root, "userdel", "-r", "anon"], capture_output=True)
            
            files = ["/etc/motd", "/etc/issue", "/usr/sbin/void-installer", "/etc/sudoers.d/99-void-live", "/etc/xbps.d/00-repo-default.conf", "/etc/xdg/autostart/svi.desktop"]
            for f in files:
                p = os.path.join(self.target_root, f.lstrip("/"))
                if os.path.exists(p): os.remove(p)

            getty_conf = os.path.join(self.target_root, "etc/sv/agetty-tty1/conf")
            if os.path.exists(getty_conf):
                with open(getty_conf, "r") as f: c = f.read()
                c = c.replace(" -a anon", "").replace("--noclear -a anon", "--noclear")
                with open(getty_conf, "w") as f: f.write(c)
        except Exception:
            pass

    def _copy_include_root(self, include_root: str):
        real_source = include_root
        
        # Fallback: Home-Verzeichnis des Live-Users
        if not os.path.exists(real_source):
            sudo_user = os.getenv("SUDO_USER")
            if sudo_user:
                alt_source = os.path.join("/home", sudo_user, "INCLUDE")
                if os.path.exists(alt_source):
                    self._log(_("Nutze alternativen Include-Pfad: {}").format(alt_source))
                    real_source = alt_source

        try:
            self._log(_("Kopiere Include-Root von {} nach {}...").format(real_source, self.target_root))
            if not os.path.exists(real_source):
                self._log(_("Info: Kein Include-Ordner gefunden (weder /opt noch Home), überspringe."))
                return

            subprocess.run(["cp", "-a", f"{real_source}/.", self.target_root], check=True)
            self._log(_("✓ Include-Root erfolgreich kopiert"))
        except Exception as e:
            self._log(_("Fehler beim Kopieren des Include-Root: {}").format(e))
            raise

    def _copy_network_configs(self):
        self._log(_("Kopiere bestehende Netzwerk-Verbindungen..."))
        try:
            source = "/etc/NetworkManager/system-connections"
            target = os.path.join(self.target_root, "etc/NetworkManager/system-connections")
            
            if os.path.exists(source) and os.path.isdir(source):
                os.makedirs(target, exist_ok=True)
                subprocess.run(f"cp -a {source}/* {target}/", shell=True, stderr=subprocess.DEVNULL)
                self._log(_("✓ WLAN-Profile übernommen."))
        except Exception as e:
            self._log(_("Info: Keine Netzwerk-Profile kopiert ({})").format(e))

    def _enter_chroot_mounts(self):
        if os.path.exists("/sys/firmware/efi"):
            self._run(["modprobe", "efivarfs"], check=False)
            self._run(["mount", "-t", "efivarfs", "efivarfs", "/sys/firmware/efi/efivars"], check=False)

        for p in ["/dev", "/proc", "/sys"]:
            target_path = os.path.join(self.target_root, p.lstrip("/"))
            os.makedirs(target_path, exist_ok=True)
            self._run(["mount", "--bind", p, target_path])

        if os.path.exists("/sys/firmware/efi"):
            efivars_target = os.path.join(self.target_root, "sys/firmware/efi/efivars")
            os.makedirs(efivars_target, exist_ok=True)
            self._run(["mount", "--bind", "/sys/firmware/efi/efivars", efivars_target], check=False)

        try:
            shutil.copy2("/etc/resolv.conf", os.path.join(self.target_root, "etc/resolv.conf"))
        except: pass
	
    def _leave_chroot_mounts(self):
        try:
            os.remove(os.path.join(self.target_root, "etc/resolv.conf"))
        except: pass

        if os.path.exists("/sys/firmware/efi"):
            self._run(["umount", os.path.join(self.target_root, "sys/firmware/efi/efivars")], check=False)

        for p in ["/dev", "/proc", "/sys"]:
            self._run(["umount", "-R", os.path.join(self.target_root, p.lstrip("/"))], check=False)
