#!/bin/bash
#######################################################################
#         ___    _    _   _ _____    ____           _     _   _
#        |_ _|  / \  | \ | |_   _|  / ___|_ __ ___ | |__ | | | |
#         | |  / _ \ |  \| | | |   | |  _| '_ ` _ \| '_ \| |_| |
#         | | / ___ \| |\  | | |   | |_| | | | | | | |_) |  _  |
#        |___/_/   \_\_| \_| |_|    \____|_| |_| |_|_.__/|_| |_|
#
#######################################################################
#
#       Eigentum der IANT GmbH. Darf nur verwendet werden im
#       Zusammenhang mit einem gültigen Wartungsvertrag mit der IANT GmbH
#       Es dürfen keine Änderungen an den Dateien ohne Zustimmung der
#       IANT GmbH durchgeführt werden.
#
#######################################################################
# Author: Benedikt Machens
# Datum: 17.07.2026
# Wingman: Gemini
#######################################################################

set -o pipefail

LOCKFILE="/var/run/iant_debian12to13.lock"
MANUAL_SCRIPT_PATH="/run/iant_debian12to13.sh"
MANUAL_UNIT="iant-debian13-upgrade-manual"
POST_REBOOT_DIR="/var/lib/iant/debian13-upgrade"
POST_REBOOT_STATE="$POST_REBOOT_DIR/post-reboot.state"
POST_REBOOT_SCRIPT="$POST_REBOOT_DIR/iant_debian12to13.sh"
POST_REBOOT_UNIT_NAME="iant-debian13-post-reboot-check.service"
POST_REBOOT_UNIT_FILE="/etc/systemd/system/$POST_REBOOT_UNIT_NAME"
DHCP_BACKUP_DIR="$POST_REBOOT_DIR/dhcp-backup"
STATE_MANAGER="/usr/lib/iant/auto-update/state-manager"

IGNORE_ARCHITECTURE=0
POST_REBOOT_CHECK_MODE=0
SBC_PRESENT=0
UPGRADE_STARTED=0
PRIMARY_INTERFACE=""
PRIMARY_MAC_ADDRESS=""
PRIMARY_IPV4_ADDRESS=""

UPGRADE_TIMERS=(
    "apt-daily.timer"
    "apt-daily-upgrade.timer"
    "iant-auto-update.timer"
    "iant-unattended-upgrade.timer"
)
ACTIVE_TIMERS=()
PINNED_INTERFACES=()
PINNED_LINK_FILES=()

show_help() {
    echo "Verwendung:"
    echo "  bash $MANUAL_SCRIPT_PATH [OPTION]"
    echo ""
    echo "Aktualisiert ein System von Debian 12 (Bookworm) auf Debian 13 (Trixie)."
    echo ""
    echo "Optionen:"
    echo "  -i, --ignore  Ausschließlich die Architekturprüfung überspringen."
    echo "                Ohne diese Option sind nur amd64 und arm64 zulässig."
    echo "  -h, --help    Diese Hilfe anzeigen und beenden."
    echo ""
    echo "Manueller Start:"
    echo "  bash $MANUAL_SCRIPT_PATH"
    echo ""
    echo "Manueller Start ohne Architekturprüfung:"
    echo "  bash $MANUAL_SCRIPT_PATH --ignore"
    echo ""
    echo "Bei einem Start in einem Terminal wird das Upgrade automatisch als"
    echo "Systemd-Service abgesichert und die Live-Ausgabe aus dem Journal angezeigt."
}

parse_arguments() {
    while [ "$#" -gt 0 ]; do
        case "$1" in
            -i|--ignore)
                IGNORE_ARCHITECTURE=1
                ;;
            -h|--help)
                show_help
                exit 0
                ;;
            --post-reboot-check)
                POST_REBOOT_CHECK_MODE=1
                ;;
            *)
                echo "FEHLER: Unbekannter Parameter: $1" >&2
                echo "Verwende --help für eine Liste der zulässigen Parameter." >&2
                exit 2
                ;;
        esac
        shift
    done
}

check_root() {
    if [ "$EUID" -ne 0 ]; then
        echo "FEHLER: Dieses Upgrade-Skript muss als root ausgeführt werden." >&2
        exit 1
    fi
}

check_bootstrap_tools() {
    local command_name
    local -a missing_tools=()

    for command_name in apt-get awk dpkg flock journalctl systemctl systemd-run; do
        if ! command -v "$command_name" >/dev/null 2>&1; then
            missing_tools+=("$command_name")
        fi
    done

    if [ "${#missing_tools[@]}" -gt 0 ]; then
        echo "FEHLER: Notwendige Basiswerkzeuge fehlen: ${missing_tools[*]}" >&2
        echo "Die fehlenden Basiswerkzeuge können nicht sicher automatisch nachinstalliert werden." >&2
        exit 1
    fi
}

secure_interactive_run() {
    local unit_name="${MANUAL_UNIT}.service"

    if [ ! -t 0 ] || [ ! -t 1 ]; then
        return
    fi

    if [ ! -f "$MANUAL_SCRIPT_PATH" ]; then
        echo "FEHLER: Das Skript wurde nicht unter $MANUAL_SCRIPT_PATH gefunden." >&2
        exit 1
    fi

    if systemctl is-active --quiet "$unit_name"; then
        echo "Das Upgrade läuft bereits als $unit_name."
    else
        systemctl reset-failed "$unit_name" 2>/dev/null || true

        echo "Starte das Upgrade abgesichert als Systemd-Service $unit_name..."
        if ! systemd-run \
            --unit="$MANUAL_UNIT" \
            --description="IANT Debian 12 auf 13 Upgrade" \
            --property=Type=exec \
            /bin/bash "$MANUAL_SCRIPT_PATH" "$@"; then
            echo "FEHLER: Der Systemd-Service $unit_name konnte nicht gestartet werden." >&2
            exit 1
        fi
    fi

    echo "Wechsle zur Live-Ausgabe des Systemd-Journals."
    echo "Strg+C beendet nur die Anzeige; das Upgrade läuft weiter."
    exec journalctl -fu "$unit_name"
}

acquire_lock() {
    if ! exec 9>"$LOCKFILE"; then
        echo "FEHLER: Lockdatei $LOCKFILE konnte nicht geöffnet werden." >&2
        exit 1
    fi

    if ! flock -n 9; then
        echo "FEHLER: Dieses Upgrade-Skript läuft bereits auf diesem System!" >&2
        exit 1
    fi
}

release_lock() {
    flock -u 9 2>/dev/null || true
}

setup_environment() {
    export DEBIAN_FRONTEND=noninteractive
    export APT_LISTCHANGES_FRONTEND=none
    export NEEDRESTART_MODE=a
}

apt_get() {
    apt-get \
        -o Acquire::Retries=3 \
        -o DPkg::Lock::Timeout=600 \
        "$@"
}

report_state() {
    local status_code="$1"
    local status_message="$2"

    if [ -x "$STATE_MANAGER" ]; then
        "$STATE_MANAGER" \
            "iant_debian12to13" "$status_code" "$status_message" || true
    fi
}

check_state_manager() {
    if [ ! -x "$STATE_MANAGER" ]; then
        error_handler "Erforderlicher IANT State-Manager fehlt oder ist nicht ausführbar: $STATE_MANAGER"
    fi
}

stop_active_timers() {
    local timer

    if [ "${#ACTIVE_TIMERS[@]}" -gt 0 ]; then
        echo "Stoppe aktive Hintergrund-Timer..."
        for timer in "${ACTIVE_TIMERS[@]}"; do
            echo "  -> systemctl stop $timer"
            systemctl stop "$timer" 2>/dev/null || true
        done
    fi
}

start_active_timers() {
    local timer

    if [ "${#ACTIVE_TIMERS[@]}" -gt 0 ]; then
        echo "Reaktiviere Hintergrund-Timer..."
        for timer in "${ACTIVE_TIMERS[@]}"; do
            echo "  -> systemctl start $timer"
            systemctl start "$timer" 2>/dev/null || true
        done
    fi
}

error_handler() {
    local error_msg="$1"

    echo "FEHLER: $error_msg" >&2

    if [ "$UPGRADE_STARTED" -eq 0 ]; then
        start_active_timers
    else
        echo "Das Trixie-Upgrade wurde bereits begonnen." >&2
        echo "Paketquellen bleiben auf Trixie und Hintergrund-Timer bleiben bis zur manuellen Prüfung gestoppt." >&2
    fi

    report_state "2" "ERROR: $error_msg"
    release_lock
    exit 1
}

check_operating_system() {
    local major_version
    local architecture

    if [ ! -f /etc/os-release ]; then
        echo "FEHLER: Betriebssystem-Version konnte nicht ermittelt werden (/etc/os-release fehlt)." >&2
        release_lock
        exit 1
    fi

    . /etc/os-release
    major_version="${VERSION_ID%%.*}"

    if ! [[ "$major_version" =~ ^[0-9]+$ ]]; then
        echo "FEHLER: Ungültige Betriebssystem-Version: ${VERSION_ID:-unbekannt}" >&2
        release_lock
        exit 1
    fi

    if [ "$major_version" -ge 13 ]; then
        echo "====== SYSTEM-CHECK: Das System läuft bereits auf Debian ${VERSION_ID} (${VERSION_CODENAME:-unbekannt})."
        echo "Kein Upgrade erforderlich. Beende Skript sicher."
        release_lock
        exit 0
    fi

    if [ "$major_version" -ne 12 ]; then
        echo "FEHLER: Dieses Upgrade-Skript unterstützt ausschließlich Debian 12 (Bookworm) als Ausgangsbasis!" >&2
        echo "Erkanntes System: Debian ${VERSION_ID:-unbekannt} (${VERSION_CODENAME:-unbekannt})" >&2
        release_lock
        exit 1
    fi

    if [ "$IGNORE_ARCHITECTURE" -eq 1 ]; then
        echo "WARNUNG: Architekturprüfung wurde durch --ignore übersprungen."
        return
    fi

    architecture=$(dpkg --print-architecture)
    case "$architecture" in
        amd64|arm64)
            ;;
        *)
            echo "FEHLER: Unzulässige Systemarchitektur erkannt!" >&2
            echo "Standardmäßig sind ausschließlich amd64 und arm64 zugelassen." >&2
            echo "Erkannte Architektur: $architecture" >&2
            echo "Mit --ignore kann die Architekturprüfung übersprungen werden." >&2
            release_lock
            exit 1
            ;;
    esac
}

ensure_required_tools() {
    local command_name
    local package_name
    local pair
    local -a missing_tools=()
    local -a missing_packages=()
    local -A package_added=()
    local -a tool_package_pairs=(
        "apt:apt"
        "apt-get:apt"
        "awk:mawk"
        "chmod:coreutils"
        "cmp:diffutils"
        "cp:coreutils"
        "date:coreutils"
        "df:coreutils"
        "dpkg:dpkg"
        "dpkg-query:dpkg"
        "find:findutils"
        "flock:util-linux"
        "gpg:gpg"
        "grep:grep"
        "ip:iproute2"
        "journalctl:systemd"
        "mkdir:coreutils"
        "mktemp:coreutils"
        "mv:coreutils"
        "reboot:systemd-sysv"
        "rm:coreutils"
        "rmdir:coreutils"
        "sed:sed"
        "sleep:coreutils"
        "systemctl:systemd"
        "systemd-run:systemd"
        "tail:coreutils"
        "tr:coreutils"
        "udevadm:udev"
        "wget:wget"
    )

    echo "Prüfe benötigte Werkzeuge..."

    for pair in "${tool_package_pairs[@]}"; do
        command_name="${pair%%:*}"
        package_name="${pair#*:}"

        if ! command -v "$command_name" >/dev/null 2>&1; then
            missing_tools+=("$command_name")

            if [ -z "${package_added[$package_name]+x}" ]; then
                missing_packages+=("$package_name")
                package_added["$package_name"]=1
            fi
        fi
    done

    if [ "${#missing_tools[@]}" -eq 0 ]; then
        echo "Alle benötigten Werkzeuge sind vorhanden."
        return
    fi

    echo "Fehlende Werkzeuge: ${missing_tools[*]}"
    echo "Installiere Pakete: ${missing_packages[*]}"

    apt_get update || error_handler "Paketlisten-Update zur Installation benötigter Werkzeuge fehlgeschlagen"
    apt_get install -y --no-install-recommends "${missing_packages[@]}" || \
        error_handler "Installation benötigter Werkzeuge fehlgeschlagen"

    for command_name in "${missing_tools[@]}"; do
        if ! command -v "$command_name" >/dev/null 2>&1; then
            error_handler "Werkzeug '$command_name' ist nach der Paketinstallation weiterhin nicht verfügbar"
        fi
    done
}

detect_3cx_sbc() {
    if dpkg-query -W -f='${Status}' 3cxsbc 2>/dev/null | grep -q "ok installed"; then
        SBC_PRESENT=1
    fi
}

get_interface_ipv4_address() {
    local interface="$1"
    local preferred_address="${2:-}"

    ip -4 -o address show dev "$interface" scope global 2>/dev/null | \
        awk -v preferred="$preferred_address" '
            {
                split($4, address, "/")
                if (first == "")
                    first = address[1]
                if (preferred != "" && address[1] == preferred) {
                    print address[1]
                    found = 1
                    exit
                }
            }
            END {
                if (!found)
                    print first
            }
        '
}

capture_network_state() {
    echo "Erfasse Netzwerkschnittstelle und IPv4-Adresse vor dem Upgrade..."

    PRIMARY_INTERFACE=$(ip -4 route show default 2>/dev/null | \
        awk '{ for (i = 1; i <= NF; i++) if ($i == "dev") { print $(i + 1); exit } }')

    if [ -z "$PRIMARY_INTERFACE" ]; then
        if [ "$SBC_PRESENT" -eq 1 ]; then
            error_handler "Für den 3CX SBC konnte keine IPv4-Standardschnittstelle ermittelt werden"
        fi

        echo "WARNUNG: Keine IPv4-Standardroute gefunden. Die Adressprüfung nach dem Neustart entfällt."
        return
    fi

    if ! [[ "$PRIMARY_INTERFACE" =~ ^[A-Za-z0-9_.-]+$ ]] || \
       [ ! -d "/sys/class/net/$PRIMARY_INTERFACE" ]; then
        error_handler "Ungültige IPv4-Standardschnittstelle erkannt: $PRIMARY_INTERFACE"
    fi

    if [ -e "/sys/class/net/$PRIMARY_INTERFACE/device" ] && \
       [ -r "/sys/class/net/$PRIMARY_INTERFACE/address" ]; then
        PRIMARY_MAC_ADDRESS=$(tr '[:upper:]' '[:lower:]' < \
            "/sys/class/net/$PRIMARY_INTERFACE/address")
    fi

    if [ -n "$PRIMARY_MAC_ADDRESS" ] && \
       ! [[ "$PRIMARY_MAC_ADDRESS" =~ ^([0-9a-f]{2}:){5}[0-9a-f]{2}$ ]]; then
        error_handler "Ungültige MAC-Adresse für $PRIMARY_INTERFACE erkannt"
    fi

    PRIMARY_IPV4_ADDRESS=$(get_interface_ipv4_address "$PRIMARY_INTERFACE")

    if [ -z "$PRIMARY_IPV4_ADDRESS" ] && [ "$SBC_PRESENT" -eq 1 ]; then
        error_handler "Für den 3CX SBC konnte keine globale IPv4-Adresse auf $PRIMARY_INTERFACE ermittelt werden"
    fi

    echo "  -> Schnittstelle: $PRIMARY_INTERFACE"
    echo "  -> IPv4-Adresse: ${PRIMARY_IPV4_ADDRESS:-nicht vorhanden}"
}

backup_dhcp_identity() {
    local lease_file

    echo "Sichere DHCP-Identität und vorhandene Lease-Dateien..."
    mkdir -p "$DHCP_BACKUP_DIR/dhclient" || \
        error_handler "Verzeichnis für die DHCP-Sicherung konnte nicht erstellt werden"
    chmod 0700 "$POST_REBOOT_DIR" "$DHCP_BACKUP_DIR" "$DHCP_BACKUP_DIR/dhclient" || \
        error_handler "Berechtigungen der DHCP-Sicherung konnten nicht gesetzt werden"

    if [ -s /etc/machine-id ]; then
        cp -a /etc/machine-id "$DHCP_BACKUP_DIR/machine-id" || \
            error_handler "Machine-ID konnte nicht gesichert werden"
    fi

    if [ -s /var/lib/dhcpcd/duid ]; then
        cp -a /var/lib/dhcpcd/duid "$DHCP_BACKUP_DIR/dhcpcd-duid" || \
            error_handler "DHCPCD-DUID konnte nicht gesichert werden"
    fi

    find "$DHCP_BACKUP_DIR/dhclient" -maxdepth 1 -type f -name 'dhclient*.leases' -delete || \
        error_handler "Alte DHCP-Lease-Sicherung konnte nicht bereinigt werden"

    for lease_file in /var/lib/dhcp/dhclient*.leases; do
        if [ ! -f "$lease_file" ]; then
            continue
        fi

        cp -a "$lease_file" "$DHCP_BACKUP_DIR/dhclient/${lease_file##*/}" || \
            error_handler "DHCP-Lease-Datei ${lease_file##*/} konnte nicht gesichert werden"
    done
}

restore_dhcp_identity() {
    local backup_file
    local target_file

    echo "Prüfe die DHCP-Identität nach dem Paket-Upgrade..."

    if [ -f "$DHCP_BACKUP_DIR/machine-id" ] && \
       ! cmp -s "$DHCP_BACKUP_DIR/machine-id" /etc/machine-id; then
        echo "WARNUNG: Die Machine-ID wurde verändert oder entfernt. Stelle den ursprünglichen Wert wieder her."
        cp -a "$DHCP_BACKUP_DIR/machine-id" /etc/machine-id || \
            error_handler "Ursprüngliche Machine-ID konnte nicht wiederhergestellt werden"
    fi

    if [ -f "$DHCP_BACKUP_DIR/dhcpcd-duid" ] && \
       ! cmp -s "$DHCP_BACKUP_DIR/dhcpcd-duid" /var/lib/dhcpcd/duid; then
        echo "WARNUNG: Die DHCPCD-DUID wurde verändert oder entfernt. Stelle den ursprünglichen Wert wieder her."
        mkdir -p /var/lib/dhcpcd || \
            error_handler "DHCPCD-Verzeichnis konnte nicht erstellt werden"
        cp -a "$DHCP_BACKUP_DIR/dhcpcd-duid" /var/lib/dhcpcd/duid || \
            error_handler "Ursprüngliche DHCPCD-DUID konnte nicht wiederhergestellt werden"
    fi

    for backup_file in "$DHCP_BACKUP_DIR"/dhclient/dhclient*.leases; do
        if [ ! -f "$backup_file" ]; then
            continue
        fi

        target_file="/var/lib/dhcp/${backup_file##*/}"
        if [ ! -f "$target_file" ]; then
            echo "WARNUNG: ${target_file##*/} fehlt. Stelle die gesicherte Lease-Datei wieder her."
            mkdir -p /var/lib/dhcp || \
                error_handler "DHCP-Lease-Verzeichnis konnte nicht erstellt werden"
            cp -a "$backup_file" "$target_file" || \
                error_handler "DHCP-Lease-Datei ${target_file##*/} konnte nicht wiederhergestellt werden"
        fi
    done
}

capture_active_timers() {
    local timer

    ACTIVE_TIMERS=()
    for timer in "${UPGRADE_TIMERS[@]}"; do
        if systemctl is-active --quiet "$timer"; then
            ACTIVE_TIMERS+=("$timer")
        fi
    done
}

prepare_package_database() {
    local audit_output

    audit_output=$(dpkg --audit)
    if [ -n "$audit_output" ]; then
        echo "Repariere unvollständig konfigurierte Pakete vor dem Upgrade..."
        echo "$audit_output"
        dpkg --force-confdef --force-confold --configure -a || \
            error_handler "Ausstehende Paketkonfiguration konnte nicht abgeschlossen werden"
    fi

    if ! apt_get check; then
        echo "Repariere bestehende Paketabhängigkeiten..."
        apt_get --fix-broken install -y \
            -o Dpkg::Options::="--force-confdef" \
            -o Dpkg::Options::="--force-confold" || \
            error_handler "Bestehende Paketabhängigkeiten konnten nicht repariert werden"
    fi

    apt_get check || error_handler "Paketdatenbank ist vor dem Upgrade nicht konsistent"
}

verify_package_state() {
    local audit_output
    local repair_needed=0

    audit_output=$(dpkg --audit)
    if [ -n "$audit_output" ]; then
        repair_needed=1
    fi

    if ! apt_get check; then
        repair_needed=1
    fi

    if [ "$repair_needed" -eq 0 ]; then
        return
    fi

    echo "Repariere den Paketstatus vor dem Neustart..."
    if [ -n "$audit_output" ]; then
        echo "$audit_output"
    fi

    dpkg --force-confdef --force-confold --configure -a || true
    apt_get --fix-broken install -y \
        -o Dpkg::Options::="--force-confdef" \
        -o Dpkg::Options::="--force-confold" \
        -o APT::listchanges::Frontend=none || \
        error_handler "Paketstatus konnte vor dem Neustart nicht repariert werden"
    dpkg --force-confdef --force-confold --configure -a || \
        error_handler "Pakete konnten vor dem Neustart nicht vollständig konfiguriert werden"

    audit_output=$(dpkg --audit)
    if [ -n "$audit_output" ]; then
        echo "$audit_output" >&2
        error_handler "Nach der Reparatur sind Pakete weiterhin nicht vollständig konfiguriert"
    fi

    apt_get check || error_handler "Paketabhängigkeiten sind nach der Reparatur nicht konsistent"
}

check_boot_space() {
    local boot_path
    local min_boot_space
    local free_space

    if grep -q " /boot/firmware " /proc/mounts; then
        boot_path="/boot/firmware"
        min_boot_space=100
    elif grep -q " /boot " /proc/mounts; then
        boot_path="/boot"
        min_boot_space=150
    else
        boot_path="/"
        min_boot_space=150
    fi

    if [ ! -d "$boot_path" ]; then
        error_handler "Ermittelter Boot-Pfad $boot_path existiert nicht auf diesem System."
    fi

    free_space=$(df -m --output=avail "$boot_path" | tail -n1 | tr -d '[:space:]')

    if ! [[ "$free_space" =~ ^[0-9]+$ ]]; then
        error_handler "Speicherplatzprüfung für $boot_path fehlgeschlagen (unerwartetes Ausgabeformat)."
    fi

    if [ "$free_space" -lt "$min_boot_space" ]; then
        error_handler "Zu wenig freier Speicherplatz auf der Boot-Partition ($boot_path)! Vorhanden: ${free_space}MB, benötigt mindestens: ${min_boot_space}MB"
    fi
}

pin_network_interface_names() {
    local sys_path
    local interface
    local mac_address
    local driver
    local safe_name
    local link_file
    local temp_file
    local link_test_output
    local predicted_name
    local applied_link_file
    local pin_reason
    local pinned_count=0

    echo "Prüfe Namen physischer Netzwerkschnittstellen für den nächsten Neustart..."
    PINNED_INTERFACES=()
    PINNED_LINK_FILES=()

    for sys_path in /sys/class/net/*; do
        if [ ! -e "$sys_path" ]; then
            continue
        fi

        interface="${sys_path##*/}"
        if [ "$interface" = "lo" ] || [ ! -e "$sys_path/device" ]; then
            continue
        fi

        if [ ! -r "$sys_path/type" ] || [ "$(<"$sys_path/type")" != "1" ] || \
           [ ! -r "$sys_path/address" ]; then
            continue
        fi

        mac_address=$(tr '[:upper:]' '[:lower:]' < "$sys_path/address")
        if ! [[ "$mac_address" =~ ^([0-9a-f]{2}:){5}[0-9a-f]{2}$ ]] || \
           [ "$mac_address" = "00:00:00:00:00:00" ]; then
            echo "WARNUNG: $interface besitzt keine geeignete MAC-Adresse. Eine feste Zuordnung ist nicht möglich."
            continue
        fi

        safe_name=$(printf '%s' "$interface" | tr -c 'A-Za-z0-9_.-' '_')
        link_file="/etc/systemd/network/00-iant-${safe_name}.link"

        if ! link_test_output=$(udevadm test-builtin net_setup_link "$sys_path" 2>/dev/null); then
            error_handler "Systemd-Namensprognose für $interface konnte nicht ausgeführt werden"
        fi

        predicted_name=$(printf '%s\n' "$link_test_output" | \
            awk -F= '$1 == "ID_NET_NAME" { name=$2 } END { print name }')
        driver=""
        if [ -r "$sys_path/device/uevent" ]; then
            driver=$(awk -F= '$1 == "DRIVER" { print $2; exit }' "$sys_path/device/uevent")
        fi
        if [ -z "$driver" ]; then
            driver=$(printf '%s\n' "$link_test_output" | \
                awk -F= '$1 == "ID_NET_DRIVER" { value=$2 } END { print value }')
        fi

        pin_reason=""
        if [ -f "$link_file" ]; then
            pin_reason="vorhandene IANT-Zuordnung"
        elif [ -n "$predicted_name" ] && [ "$predicted_name" != "$interface" ]; then
            pin_reason="Systemd prognostiziert den Namen $predicted_name"
        elif [ "$driver" = "i40e" ]; then
            pin_reason="i40e kann den Namen erst mit dem neuen Kernel ändern"
        elif [ "$SBC_PRESENT" -eq 1 ] && [ "$interface" = "$PRIMARY_INTERFACE" ]; then
            pin_reason="primäre Schnittstelle des 3CX SBC"
        else
            echo "  -> $interface: ${predicted_name:-aktueller Name} bleibt Standard; keine .link-Datei nötig."
            continue
        fi

        mkdir -p /etc/systemd/network || \
            error_handler "Verzeichnis für Systemd-Linkdateien konnte nicht erstellt werden"
        temp_file=$(mktemp "${link_file}.XXXXXX") || \
            error_handler "Temporäre Linkdatei für $interface konnte nicht erstellt werden"

        if ! {
            echo "# Erstellt durch iant_debian12to13.sh"
            echo "[Match]"
            echo "MACAddress=$mac_address"
            echo ""
            echo "[Link]"
            echo "Name=$interface"
        } > "$temp_file"; then
            rm -f "$temp_file"
            error_handler "Linkdatei für $interface konnte nicht geschrieben werden"
        fi

        if ! chmod 0644 "$temp_file" || ! mv -f "$temp_file" "$link_file"; then
            rm -f "$temp_file"
            error_handler "Linkdatei für $interface konnte nicht installiert werden"
        fi

        if ! link_test_output=$(udevadm test-builtin net_setup_link "$sys_path" 2>/dev/null); then
            rm -f "$link_file"
            error_handler "Systemd-Linkkonfiguration für $interface konnte nicht geprüft werden"
        fi

        predicted_name=$(printf '%s\n' "$link_test_output" | \
            awk -F= '$1 == "ID_NET_NAME" { name=$2 } END { print name }')
        applied_link_file=$(printf '%s\n' "$link_test_output" | \
            awk -F= '$1 == "ID_NET_LINK_FILE" { file=$2 } END { print file }')

        if [ "$applied_link_file" != "$link_file" ]; then
            rm -f "$link_file"
            error_handler "Linkdatei für $interface wird von Systemd nicht verwendet (verwendet: ${applied_link_file:-keine})"
        fi

        if [ "$predicted_name" != "$interface" ]; then
            rm -f "$link_file"
            error_handler "Netzwerkschnittstelle $interface würde nach dem Neustart als ${predicted_name:-unbekannt} benannt"
        fi

        echo "  -> $interface bleibt über $mac_address fest zugeordnet ($pin_reason)."
        PINNED_INTERFACES+=("$interface")
        PINNED_LINK_FILES+=("$link_file")
        pinned_count=$((pinned_count + 1))
    done

    if [ "$pinned_count" -eq 0 ]; then
        echo "Keine zusätzliche .link-Datei erforderlich."
    fi
}

verify_pinned_network_interface_names() {
    local index
    local interface
    local link_file
    local sys_path
    local link_test_output
    local predicted_name
    local applied_link_file

    if [ "${#PINNED_INTERFACES[@]}" -eq 0 ]; then
        return
    fi

    echo "Prüfe gesicherte Netzwerkschnittstellennamen mit Systemd aus Trixie..."
    for index in "${!PINNED_INTERFACES[@]}"; do
        interface="${PINNED_INTERFACES[$index]}"
        link_file="${PINNED_LINK_FILES[$index]}"
        sys_path="/sys/class/net/$interface"

        if [ ! -e "$sys_path" ] || [ ! -f "$link_file" ]; then
            error_handler "Namenssicherung für Netzwerkschnittstelle $interface ist nicht mehr vollständig"
        fi

        if ! link_test_output=$(udevadm test-builtin net_setup_link "$sys_path" 2>/dev/null); then
            error_handler "Systemd-Linkkonfiguration für $interface konnte nach dem Upgrade nicht geprüft werden"
        fi

        predicted_name=$(printf '%s\n' "$link_test_output" | \
            awk -F= '$1 == "ID_NET_NAME" { name=$2 } END { print name }')
        applied_link_file=$(printf '%s\n' "$link_test_output" | \
            awk -F= '$1 == "ID_NET_LINK_FILE" { file=$2 } END { print file }')

        if [ "$applied_link_file" != "$link_file" ] || [ "$predicted_name" != "$interface" ]; then
            error_handler "Netzwerkschnittstelle $interface ist für den Neustart nicht sicher auf ihren bisherigen Namen festgelegt"
        fi

        echo "  -> $interface bleibt nach dem Neustart unverändert."
    done
}

stop_3cx_sbc() {
    if [ "$SBC_PRESENT" -eq 1 ]; then
        echo "3CX SBC erkannt. Stoppe Dienst vor Upgrade..."
        systemctl stop 3cxsbc || true
    fi
}

update_debian_12_packages() {
    echo "Aktualisiere Debian 12 vollständig..."
    apt_get update || error_handler "Paketlisten-Update fehlgeschlagen (Debian 12)"
    apt_get dist-upgrade -y \
        -o Dpkg::Options::="--force-confdef" \
        -o Dpkg::Options::="--force-confold" \
        -o APT::listchanges::Frontend=none || \
        error_handler "Vollständiges Debian-12-Upgrade fehlgeschlagen"

    verify_package_state
}

switch_package_sources_to_trixie() {
    echo "Passe Paketquellen auf Debian 13 (Trixie) an..."
    UPGRADE_STARTED=1

    if [ -f /etc/apt/sources.list ]; then
        sed -i 's/bookworm/trixie/g' /etc/apt/sources.list || \
            error_handler "Paketquelle /etc/apt/sources.list konnte nicht angepasst werden"
    fi

    if [ -d /etc/apt/sources.list.d ]; then
        find /etc/apt/sources.list.d/ -type f \
            \( -name "*.list" -o -name "*.sources" \) \
            -exec sed -i 's/bookworm/trixie/g' {} + || \
            error_handler "Paketquellen unter /etc/apt/sources.list.d konnten nicht vollständig angepasst werden"
    fi
}

update_3cx_repository_key() {
    local public_key_file
    local keyring_file

    if [ "$SBC_PRESENT" -ne 1 ]; then
        return
    fi

    echo "Aktualisiere GPG-Schlüssel für das 3CX-Repository..."
    mkdir -p /usr/share/keyrings || \
        error_handler "Verzeichnis /usr/share/keyrings konnte nicht erstellt werden"

    public_key_file=$(mktemp /run/3cx-key.XXXXXX) || \
        error_handler "Temporäre Datei für den 3CX-Schlüssel konnte nicht erstellt werden"
    keyring_file=$(mktemp /usr/share/keyrings/.3cx-archive-keyring.gpg.XXXXXX) || {
        rm -f "$public_key_file"
        error_handler "Temporärer 3CX-Keyring konnte nicht erstellt werden"
    }

    if ! wget --tries=3 --timeout=30 -qO "$public_key_file" https://repo.3cx.com/key.pub || \
       ! gpg --batch --yes --dearmor --output "$keyring_file" "$public_key_file" || \
       [ ! -s "$keyring_file" ]; then
        rm -f "$public_key_file" "$keyring_file"
        error_handler "GPG-Schlüssel für das 3CX-Repository konnte nicht aktualisiert werden"
    fi

    chmod 0644 "$keyring_file" || {
        rm -f "$public_key_file" "$keyring_file"
        error_handler "Berechtigungen des 3CX-Keyrings konnten nicht gesetzt werden"
    }
    mv -f "$keyring_file" /usr/share/keyrings/3cx-archive-keyring.gpg || {
        rm -f "$public_key_file" "$keyring_file"
        error_handler "3CX-Keyring konnte nicht atomar ersetzt werden"
    }
    rm -f "$public_key_file"
}

repair_incomplete_upgrade() {
    echo "Versuche unvollständiges Trixie-Upgrade automatisch fortzusetzen..."

    dpkg --force-confdef --force-confold --configure -a || true
    apt_get --fix-broken install -y \
        -o Dpkg::Options::="--force-confdef" \
        -o Dpkg::Options::="--force-confold" \
        -o APT::listchanges::Frontend=none || return 1
    dpkg --force-confdef --force-confold --configure -a || return 1
    apt_get dist-upgrade -y \
        -o Dpkg::Options::="--force-confdef" \
        -o Dpkg::Options::="--force-confold" \
        -o APT::listchanges::Frontend=none
}

upgrade_to_debian_13() {
    echo "Aktualisiere Paketlisten für Debian 13 (Trixie)..."
    apt_get update || error_handler "Paketlisten-Update für Trixie fehlgeschlagen"

    echo "Führe minimales Trixie-Upgrade durch..."
    if ! apt_get upgrade -y \
        -o Dpkg::Options::="--force-confdef" \
        -o Dpkg::Options::="--force-confold" \
        -o APT::listchanges::Frontend=none; then
        if ! repair_incomplete_upgrade; then
            error_handler "Minimales Upgrade auf Debian 13 fehlgeschlagen"
        fi
        return 0
    fi

    echo "Führe vollständiges Trixie-Upgrade durch..."
    if ! apt_get dist-upgrade -y \
        -o Dpkg::Options::="--force-confdef" \
        -o Dpkg::Options::="--force-confold" \
        -o APT::listchanges::Frontend=none; then
        repair_incomplete_upgrade || error_handler "Dist-Upgrade auf Debian 13 fehlgeschlagen"
    fi
}

cleanup_unused_packages() {
    echo "Entferne nicht mehr benötigte Pakete..."
    apt_get autoremove -y \
        -o Dpkg::Options::="--force-confdef" \
        -o Dpkg::Options::="--force-confold" || \
        error_handler "Nicht mehr benötigte Pakete konnten nicht entfernt werden"

    echo "Bereinige den APT-Paketcache..."
    apt_get clean || error_handler "APT-Paketcache konnte nicht bereinigt werden"
}

source_option_deb822_field() {
    case "${1,,}" in
        arch)                         echo "Architectures" ;;
        arch+)                        echo "Architectures-Add" ;;
        arch-)                        echo "Architectures-Remove" ;;
        lang)                         echo "Languages" ;;
        lang+)                        echo "Languages-Add" ;;
        lang-)                        echo "Languages-Remove" ;;
        target)                       echo "Targets" ;;
        target+)                      echo "Targets-Add" ;;
        target-)                      echo "Targets-Remove" ;;
        pdiffs)                       echo "PDiffs" ;;
        by-hash)                      echo "By-Hash" ;;
        allow-insecure)               echo "Allow-Insecure" ;;
        allow-weak)                   echo "Allow-Weak" ;;
        allow-downgrade-to-insecure)  echo "Allow-Downgrade-To-Insecure" ;;
        check-valid-until)            echo "Check-Valid-Until" ;;
        valid-until-min)              echo "Valid-Until-Min" ;;
        valid-until-max)              echo "Valid-Until-Max" ;;
        check-date)                   echo "Check-Date" ;;
        date-max-future)              echo "Date-Max-Future" ;;
        inrelease-path)               echo "InRelease-Path" ;;
        snapshot)                     echo "Snapshot" ;;
        signed-by|trusted)            return 1 ;;
        *)                            return 2 ;;
    esac
}

validate_source_options_for_modernization() {
    local source_file
    local target_file
    local line
    local option_block
    local option
    local option_name
    local option_value
    local map_status
    local active_entries
    local key
    local -a source_options=()
    local -A option_values=()
    local -A option_counts=()
    local -A line_options=()

    for source_file in "$@"; do
        if [ "$source_file" != "/etc/apt/sources.list" ]; then
            target_file="${source_file%.list}.sources"
            if [ -e "$target_file" ]; then
                echo "WARNUNG: $target_file existiert bereits neben $source_file."
                echo "Die automatische Quellenkonvertierung wird zur Vermeidung doppelter Einträge übersprungen."
                return 1
            fi
        fi

        active_entries=0
        option_values=()
        option_counts=()

        while IFS= read -r line || [ -n "$line" ]; do
            if ! [[ "$line" =~ ^[[:space:]]*deb(-src)?[[:space:]] ]]; then
                continue
            fi

            active_entries=$((active_entries + 1))
            line_options=()

            if ! [[ "$line" =~ ^[[:space:]]*deb(-src)?[[:space:]]+\[([^]]*)\] ]]; then
                continue
            fi

            option_block="${BASH_REMATCH[2]}"
            read -r -a source_options <<< "$option_block"

            for option in "${source_options[@]}"; do
                if [[ "$option" != *=* ]]; then
                    echo "WARNUNG: Nicht auswertbare APT-Option '$option' in $source_file."
                    return 1
                fi

                option_name="${option%%=*}"
                option_name="${option_name,,}"
                option_value="${option#*=}"

                if source_option_deb822_field "$option_name" >/dev/null; then
                    if [ "$source_file" = "/etc/apt/sources.list" ]; then
                        echo "WARNUNG: APT-Option '$option_name' in /etc/apt/sources.list kann nicht eindeutig automatisch übertragen werden."
                        return 1
                    fi

                    if [ -n "${line_options[$option_name]+x}" ]; then
                        echo "WARNUNG: Doppelte APT-Option '$option_name' in $source_file."
                        return 1
                    fi
                    line_options["$option_name"]=1

                    if [ -n "${option_values[$option_name]+x}" ] && \
                       [ "${option_values[$option_name]}" != "$option_value" ]; then
                        echo "WARNUNG: Unterschiedliche Werte für APT-Option '$option_name' in $source_file."
                        return 1
                    fi

                    option_values["$option_name"]="$option_value"
                    option_counts["$option_name"]=$(( ${option_counts[$option_name]:-0} + 1 ))
                else
                    map_status=$?
                    if [ "$map_status" -eq 1 ]; then
                        continue
                    fi

                    echo "WARNUNG: APT-Option '$option_name' in $source_file wird von der sicheren Konvertierung nicht unterstützt."
                    return 1
                fi
            done
        done < "$source_file"

        for key in "${!option_values[@]}"; do
            if [ "${option_counts[$key]}" -ne "$active_entries" ]; then
                echo "WARNUNG: APT-Option '$key' ist nicht für alle Einträge in $source_file identisch gesetzt."
                return 1
            fi
        done
    done
}

add_deb822_field_to_all_stanzas() {
    local source_file="$1"
    local field_name="$2"
    local field_value="$3"
    local temp_file

    temp_file=$(mktemp "${source_file}.XXXXXX") || return 1

    if ! awk -v field_name="$field_name" -v field_value="$field_value" '
        BEGIN {
            in_stanza = 0
            has_field = 0
            field_prefix = tolower(field_name) ":"
        }
        /^[[:space:]]*#/ {
            print
            next
        }
        /^[[:space:]]*$/ {
            if (in_stanza && !has_field)
                print field_name ": " field_value
            print
            in_stanza = 0
            has_field = 0
            next
        }
        {
            in_stanza = 1
            if (index(tolower($0), field_prefix) == 1)
                has_field = 1
            print
        }
        END {
            if (in_stanza && !has_field)
                print field_name ": " field_value
        }
    ' "$source_file" > "$temp_file"; then
        rm -f "$temp_file"
        return 1
    fi

    if ! chmod 0644 "$temp_file" || ! mv -f "$temp_file" "$source_file"; then
        rm -f "$temp_file"
        return 1
    fi
}

restore_modernized_source_options() {
    local original_file
    local backup_file
    local target_file
    local line
    local option_block
    local option
    local option_name
    local option_value
    local field_name
    local active_entries
    local -a source_options=()
    local -A field_values=()

    for original_file in "$@"; do
        if [ "$original_file" = "/etc/apt/sources.list" ]; then
            continue
        fi

        backup_file="${original_file}.bak"
        target_file="${original_file%.list}.sources"
        active_entries=0
        field_values=()

        if [ ! -f "$backup_file" ]; then
            echo "WARNUNG: Erwartete Sicherung $backup_file wurde nicht erstellt."
            return 1
        fi

        while IFS= read -r line || [ -n "$line" ]; do
            if ! [[ "$line" =~ ^[[:space:]]*deb(-src)?[[:space:]] ]]; then
                continue
            fi

            active_entries=$((active_entries + 1))
            if ! [[ "$line" =~ ^[[:space:]]*deb(-src)?[[:space:]]+\[([^]]*)\] ]]; then
                continue
            fi

            option_block="${BASH_REMATCH[2]}"
            read -r -a source_options <<< "$option_block"
            for option in "${source_options[@]}"; do
                option_name="${option%%=*}"
                option_name="${option_name,,}"
                option_value="${option#*=}"

                if field_name=$(source_option_deb822_field "$option_name"); then
                    case "$field_name" in
                        Architectures*|Languages*|Targets*)
                            option_value="${option_value//,/ }"
                            ;;
                    esac
                    field_values["$field_name"]="$option_value"
                fi
            done
        done < "$backup_file"

        if [ "$active_entries" -gt 0 ]; then
            if [ ! -s "$target_file" ] || \
               ! grep -qE '^[[:space:]]*Types:' "$target_file" || \
               ! grep -qE '^[[:space:]]*URIs:' "$target_file"; then
                echo "WARNUNG: Konvertiertes Ziel $target_file ist unvollständig."
                return 1
            fi
        fi

        for field_name in "${!field_values[@]}"; do
            echo "  -> Übernehme $field_name aus $backup_file."
            add_deb822_field_to_all_stanzas \
                "$target_file" "$field_name" "${field_values[$field_name]}" || return 1
        done
    done
}

modernize_package_sources() {
    local legacy_sources=0
    local source_file
    local -a legacy_files=()

    if ! apt --help 2>&1 | grep -q "modernize-sources"; then
        echo "Befehl 'apt modernize-sources' nicht verfügbar. Überspringe Konvertierung..."
        return
    fi

    if [ -f /etc/apt/sources.list ]; then
        legacy_files+=("/etc/apt/sources.list")
    fi
    if [ -d /etc/apt/sources.list.d ]; then
        while IFS= read -r -d '' source_file; do
            legacy_files+=("$source_file")
        done < <(find /etc/apt/sources.list.d -type f -name "*.list" -print0)
    fi

    if ! validate_source_options_for_modernization "${legacy_files[@]}"; then
        echo "WARNUNG: Paketquellen bleiben unverändert im alten Format erhalten."
        apt_get update || error_handler "Bestehende Paketquellen sind nicht verwendbar"
        return
    fi

    echo "Überführe Paketquellen in das DEB822-Format (.sources)..."
    if ! apt modernize-sources -y; then
        echo "WARNUNG: Paketquellen konnten nicht vollständig modernisiert werden. Alte Dateien bleiben erhalten."
        apt_get update || error_handler "Paketquellen sind nach fehlgeschlagener Konvertierung nicht verwendbar"
        return
    fi

    restore_modernized_source_options "${legacy_files[@]}" || \
        error_handler "APT-Optionen konnten nicht vollständig in das DEB822-Format übertragen werden"

    if [ -f /etc/apt/sources.list.d/raspi.sources ] && \
       grep -qE "^[[:space:]]*Signed-By:[[:space:]]*$" /etc/apt/sources.list.d/raspi.sources; then
        echo "Ergänze fehlenden GPG-Schlüsselpfad in raspi.sources..."
        sed -i 's|^[[:space:]]*Signed-By:[[:space:]]*$|Signed-By: /usr/share/keyrings/raspberrypi-archive-keyring.gpg|' \
            /etc/apt/sources.list.d/raspi.sources || \
            error_handler "GPG-Schlüsselpfad in raspi.sources konnte nicht ergänzt werden"
    fi

    echo "Prüfe modernisierte Paketquellen..."
    apt_get update || error_handler "Modernisierte Paketquellen konnten nicht validiert werden"

    if [ -f /etc/apt/sources.list ] && \
       grep -qE '^[[:space:]]*deb(-src)?[[:space:]]' /etc/apt/sources.list; then
        legacy_sources=1
    fi

    if [ -d /etc/apt/sources.list.d ] && \
       find /etc/apt/sources.list.d -type f -name "*.list" -print -quit | grep -q .; then
        legacy_sources=1
    fi

    if [ "$legacy_sources" -eq 1 ]; then
        echo "WARNUNG: Mindestens eine aktive Paketquelle blieb im alten Format. Sicherungsdateien bleiben erhalten."
        return
    fi

    echo "Konvertierung erfolgreich. Entferne veraltete Sicherungsdateien..."
    rm -f /etc/apt/sources.list.bak /etc/apt/sources.list~ || \
        error_handler "Veraltete Sicherungsdateien der Paketquellen konnten nicht entfernt werden"

    if [ -d /etc/apt/sources.list.d ]; then
        find /etc/apt/sources.list.d -type f \
            \( -name "*.bak" -o -name "*.save" -o -name "*.dpkg-*" \) \
            -delete || error_handler "Veraltete Sicherungsdateien unter /etc/apt/sources.list.d konnten nicht entfernt werden"
    fi
}

write_post_reboot_state() {
    local temp_file

    mkdir -p "$POST_REBOOT_DIR" || \
        error_handler "Verzeichnis für die Prüfung nach dem Neustart konnte nicht erstellt werden"
    chmod 0700 "$POST_REBOOT_DIR" || \
        error_handler "Berechtigungen für die Prüfung nach dem Neustart konnten nicht gesetzt werden"

    temp_file=$(mktemp "${POST_REBOOT_STATE}.XXXXXX") || \
        error_handler "Temporäre Statusdatei für die Prüfung nach dem Neustart konnte nicht erstellt werden"

    if ! {
        echo "EXPECTED_INTERFACE=$PRIMARY_INTERFACE"
        echo "EXPECTED_MAC_ADDRESS=$PRIMARY_MAC_ADDRESS"
        echo "EXPECTED_IPV4_ADDRESS=$PRIMARY_IPV4_ADDRESS"
        echo "SBC_PRESENT=$SBC_PRESENT"
    } > "$temp_file"; then
        rm -f "$temp_file"
        error_handler "Status für die Prüfung nach dem Neustart konnte nicht geschrieben werden"
    fi

    if ! chmod 0600 "$temp_file" || ! mv -f "$temp_file" "$POST_REBOOT_STATE"; then
        rm -f "$temp_file"
        error_handler "Status für die Prüfung nach dem Neustart konnte nicht installiert werden"
    fi
}

install_post_reboot_check() {
    local script_source="$0"
    local temp_file

    echo "Richte einmalige Prüfung nach dem Neustart ein..."
    write_post_reboot_state

    if [ ! -f "$script_source" ] || \
       ! grep -qF 'POST_REBOOT_CHECK_MODE=0' "$script_source"; then
        error_handler "Skriptquelle konnte nicht für die Prüfung nach dem Neustart gesichert werden; das Skript darf nicht über eine Pipe gestartet werden"
    fi

    cp -a "$script_source" "$POST_REBOOT_SCRIPT" || \
        error_handler "Upgrade-Skript konnte nicht für die Prüfung nach dem Neustart gesichert werden"
    chmod 0700 "$POST_REBOOT_SCRIPT" || \
        error_handler "Berechtigungen des Skripts für die Prüfung nach dem Neustart konnten nicht gesetzt werden"

    temp_file=$(mktemp "${POST_REBOOT_UNIT_FILE}.XXXXXX") || \
        error_handler "Temporäre Systemd-Unit für die Prüfung nach dem Neustart konnte nicht erstellt werden"

    if ! {
        echo "[Unit]"
        echo "Description=IANT Debian 13 Prüfung nach Neustart"
        echo "Wants=network-online.target"
        echo "After=network-online.target"
        if [ "$SBC_PRESENT" -eq 1 ]; then
            echo "After=3cxsbc.service"
        fi
        echo "ConditionPathExists=$POST_REBOOT_STATE"
        echo ""
        echo "[Service]"
        echo "Type=oneshot"
        echo "ExecStart=/bin/bash $POST_REBOOT_SCRIPT --post-reboot-check"
        echo "TimeoutStartSec=6min"
        echo "StandardOutput=journal"
        echo "StandardError=journal"
        echo ""
        echo "[Install]"
        echo "WantedBy=multi-user.target"
    } > "$temp_file"; then
        rm -f "$temp_file"
        error_handler "Systemd-Unit für die Prüfung nach dem Neustart konnte nicht geschrieben werden"
    fi

    if ! chmod 0644 "$temp_file" || ! mv -f "$temp_file" "$POST_REBOOT_UNIT_FILE"; then
        rm -f "$temp_file"
        error_handler "Systemd-Unit für die Prüfung nach dem Neustart konnte nicht installiert werden"
    fi

    systemctl daemon-reload || \
        error_handler "Systemd konnte die Prüfung nach dem Neustart nicht laden"
    systemctl enable "$POST_REBOOT_UNIT_NAME" >/dev/null || \
        error_handler "Prüfung nach dem Neustart konnte nicht aktiviert werden"
}

post_reboot_state_value() {
    local key="$1"

    awk -F= -v key="$key" \
        '$1 == key { print substr($0, length(key) + 2); exit }' \
        "$POST_REBOOT_STATE"
}

find_interface_by_mac() {
    local expected_mac="$1"
    local sys_path
    local current_mac

    for sys_path in /sys/class/net/*; do
        if [ ! -r "$sys_path/address" ]; then
            continue
        fi

        current_mac=$(tr '[:upper:]' '[:lower:]' < "$sys_path/address")
        if [ "$current_mac" = "$expected_mac" ]; then
            echo "${sys_path##*/}"
            return 0
        fi
    done

    return 1
}

cleanup_post_reboot_artifacts() {
    systemctl disable "$POST_REBOOT_UNIT_NAME" >/dev/null 2>&1 || true
    rm -f \
        "/etc/systemd/system/multi-user.target.wants/$POST_REBOOT_UNIT_NAME" \
        "$POST_REBOOT_UNIT_FILE" \
        "$POST_REBOOT_STATE" \
        "$POST_REBOOT_SCRIPT"

    if [ -d "$DHCP_BACKUP_DIR" ]; then
        find "$DHCP_BACKUP_DIR" -type f -delete 2>/dev/null || true
        find "$DHCP_BACKUP_DIR" -depth -type d -empty -delete 2>/dev/null || true
    fi
    rmdir "$POST_REBOOT_DIR" 2>/dev/null || true
    systemctl daemon-reload >/dev/null 2>&1 || true
}

post_reboot_failure() {
    local error_msg="$1"

    echo "FEHLER: $error_msg" >&2
    report_state "2" "ERROR: Prüfung nach Neustart: $error_msg"
    return 1
}

run_post_reboot_check() {
    local major_version
    local command_name
    local expected_interface
    local expected_mac
    local expected_ipv4
    local expected_sbc
    local current_interface=""
    local current_mac=""
    local current_ipv4=""
    local audit_output
    local attempt

    echo "====== [$(date +"%H:%M:%S")] START: Prüfung nach Debian-13-Neustart"

    if [ ! -x "$STATE_MANAGER" ]; then
        echo "FEHLER: Erforderlicher IANT State-Manager fehlt oder ist nicht ausführbar: $STATE_MANAGER" >&2
        return 1
    fi

    for command_name in awk dpkg ip systemctl tr; do
        if ! command -v "$command_name" >/dev/null 2>&1; then
            post_reboot_failure "Benötigtes Werkzeug '$command_name' fehlt"
            return 1
        fi
    done

    if [ ! -f "$POST_REBOOT_STATE" ]; then
        post_reboot_failure "Persistenter Prüfstatus fehlt"
        return 1
    fi

    if [ ! -f /etc/os-release ]; then
        post_reboot_failure "/etc/os-release fehlt"
        return 1
    fi

    . /etc/os-release
    major_version="${VERSION_ID%%.*}"
    if [ "$major_version" != "13" ]; then
        post_reboot_failure "Erwartet wurde Debian 13, erkannt wurde ${VERSION_ID:-unbekannt}"
        return 1
    fi

    expected_interface=$(post_reboot_state_value "EXPECTED_INTERFACE")
    expected_mac=$(post_reboot_state_value "EXPECTED_MAC_ADDRESS")
    expected_ipv4=$(post_reboot_state_value "EXPECTED_IPV4_ADDRESS")
    expected_sbc=$(post_reboot_state_value "SBC_PRESENT")

    if ! [[ "$expected_sbc" =~ ^[01]$ ]] || \
       { [ -n "$expected_interface" ] && ! [[ "$expected_interface" =~ ^[A-Za-z0-9_.-]+$ ]]; } || \
       { [ -n "$expected_mac" ] && ! [[ "$expected_mac" =~ ^([0-9a-f]{2}:){5}[0-9a-f]{2}$ ]]; } || \
       { [ -n "$expected_ipv4" ] && ! [[ "$expected_ipv4" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; }; then
        post_reboot_failure "Persistenter Prüfstatus ist ungültig"
        return 1
    fi

    for ((attempt = 1; attempt <= 60; attempt++)); do
        current_interface=""
        current_mac=""
        current_ipv4=""

        if [ -n "$expected_interface" ] && \
           [ -r "/sys/class/net/$expected_interface/address" ]; then
            current_mac=$(tr '[:upper:]' '[:lower:]' < \
                "/sys/class/net/$expected_interface/address")
            if [ -z "$expected_mac" ] || [ "$current_mac" = "$expected_mac" ]; then
                current_interface="$expected_interface"
            fi
        fi

        if [ -z "$current_interface" ] && [ -n "$expected_mac" ]; then
            current_interface=$(find_interface_by_mac "$expected_mac" || true)
        fi

        if [ -n "$expected_interface" ] && \
           [ -n "$current_interface" ] && \
           [ "$current_interface" != "$expected_interface" ]; then
            break
        fi

        if [ -n "$current_interface" ]; then
            current_ipv4=$(get_interface_ipv4_address "$current_interface" "$expected_ipv4")
        fi

        if { [ -z "$expected_interface" ] || [ -n "$current_interface" ]; } && \
           { [ -z "$expected_ipv4" ] || [ -n "$current_ipv4" ]; } && \
           { [ "$expected_sbc" -eq 0 ] || systemctl is-active --quiet 3cxsbc.service; }; then
            break
        fi

        sleep 5
    done

    if [ -n "$expected_interface" ] && [ -z "$current_interface" ]; then
        post_reboot_failure "Netzwerkschnittstelle $expected_interface wurde nicht gefunden"
        return 1
    fi

    if [ -n "$expected_interface" ] && [ "$current_interface" != "$expected_interface" ]; then
        post_reboot_failure "Netzwerkschnittstelle $expected_interface heißt nach dem Neustart $current_interface"
        return 1
    fi

    if [ -n "$expected_ipv4" ] && [ -z "$current_ipv4" ]; then
        post_reboot_failure "Netzwerkschnittstelle $expected_interface hat keine globale IPv4-Adresse"
        return 1
    fi

    if [ "$expected_sbc" -eq 1 ] && ! systemctl is-active --quiet 3cxsbc.service; then
        post_reboot_failure "3cxsbc.service ist nicht aktiv"
        return 1
    fi

    audit_output=$(dpkg --audit)
    if [ -n "$audit_output" ]; then
        echo "$audit_output" >&2
        post_reboot_failure "Paketdatenbank ist nicht vollständig konfiguriert"
        return 1
    fi

    cleanup_post_reboot_artifacts

    if [ -n "$expected_ipv4" ] && [ "$current_ipv4" != "$expected_ipv4" ]; then
        echo "WARNUNG: IPv4-Adresse wurde von $expected_ipv4 auf $current_ipv4 geändert."
        report_state "1" "WARNING: IPv4 nach Debian-13-Upgrade geändert: $expected_ipv4 -> $current_ipv4"
        return 0
    fi

    report_state "0" "SUCCESS: Debian 13 und Neustart geprüft"
    echo "====== [$(date +"%H:%M:%S")] ERFOLG: Debian 13 und Neustart geprüft."
    return 0
}

finish_upgrade() {
    echo "Die Abschlussmeldung erfolgt nach der Prüfung des Neustarts."
    report_state "1" "PENDING: Debian 13 installiert; Prüfung nach Neustart ausstehend"
    release_lock

    echo "====== [$(date +"%H:%M:%S")] UPGRADE INSTALLIERT: System-Neustart erfolgt in 30 Sekunden."
    sleep 30
    reboot || error_handler "System-Neustart konnte nicht ausgelöst werden"
}

main() {
    parse_arguments "$@"
    check_root

    if [ "$POST_REBOOT_CHECK_MODE" -eq 1 ]; then
        run_post_reboot_check
        return $?
    fi

    check_bootstrap_tools
    secure_interactive_run "$@"
    acquire_lock
    setup_environment

    check_operating_system

    echo "====== [$(date +"%H:%M:%S")] START: Upgrade auf Debian 13 (Trixie)"

    capture_active_timers
    stop_active_timers
    prepare_package_database
    ensure_required_tools
    check_state_manager
    detect_3cx_sbc
    check_boot_space
    capture_network_state
    backup_dhcp_identity

    stop_3cx_sbc
    update_debian_12_packages
    switch_package_sources_to_trixie
    update_3cx_repository_key
    upgrade_to_debian_13
    restore_dhcp_identity
    cleanup_unused_packages
    modernize_package_sources
    verify_package_state
    pin_network_interface_names
    verify_pinned_network_interface_names
    check_state_manager
    install_post_reboot_check
    finish_upgrade
}

main "$@"
