#!/usr/bin/env bash
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║  setup/reset-arch.sh — Wipe and reinstall the OS while keeping user data  ║
# ║                                                                              ║
# ║  PURPOSE:                                                                    ║
# ║    Performs a clean OS reinstall on an existing btrfs-on-LUKS system       ║
# ║    WITHOUT destroying user home directories or passwords. Useful when      ║
# ║    the system is broken, corrupted, or needs a clean slate.                ║
# ║                                                                              ║
# ║  WHAT THIS DOES:                                                             ║
# ║    1. Detects LUKS encryption; unlocks via FIDO2 token and/or passphrase  ║
# ║    2. Saves user credentials and system config from the old @ subvolume    ║
# ║    3. Clears app configs (~/.config) from @home, preserving auth keys     ║
# ║       (Yubico/ is kept so FIDO2 login works after the reset)              ║
# ║    4. Deletes and recreates the @ (root) btrfs subvolume                  ║
# ║    5. Reinstalls the base system via pacstrap                              ║
# ║    6. Restores credentials, PAM, fstab, mkinitcpio, GRUB config          ║
# ║    7. Regenerates initramfs and GRUB menu from chroot                     ║
# ║                                                                              ║
# ║  REQUIREMENTS:                                                               ║
# ║    - Run from an Arch Linux live environment (archiso USB)                 ║
# ║    - Target disk uses btrfs with @ and @home subvolumes                   ║
# ║    - Optional: LUKS2 encryption on the root partition                     ║
# ║                                                                              ║
# ║  WARNING: The root subvolume (@ i.e. /etc, /usr, /var) is DESTROYED.     ║
# ║           User home data (@home) is preserved. Run with extreme caution.  ║
# ╚══════════════════════════════════════════════════════════════════════════════╝

# reset-arch.sh — Reset the root btrfs subvolume while preserving user home data.
#
# What this does:
#   1. Detects LUKS encryption; unlocks via FIDO2 token and/or passphrase
#   2. Saves user credentials and system config from the old @ subvolume
#   3. Clears app configs (~/.config) from @home, preserving auth keys (Yubico/)
#   4. Deletes and recreates the @ (root) btrfs subvolume
#   5. Reinstalls the base system via pacstrap
#   6. Restores credentials, PAM, fstab, mkinitcpio, GRUB config
#   7. Regenerates initramfs and GRUB menu from chroot so the system boots cleanly

set -euo pipefail
# -e: abort immediately on any error — wrong moves here could corrupt the system
# -u: error on unset variable references
# -o pipefail: catch failures in pipes

# ── Temporary working directory ───────────────────────────────────────────────
# All temporary files (saved configs, mount points) go here.
# Cleaned up automatically on EXIT, INT, or error via trap.
TMPDIR=$(mktemp -d /tmp/arch-reset.XXXXXX)
trap 'rm -rf "$TMPDIR"' EXIT

# Simple "pause and wait for user" helper
pause() { read -rp "Press ENTER to continue..."; }

# ── Banner ────────────────────────────────────────────────────────────────────
echo "======================================="
echo "  M-Archy System Reset"
echo "======================================="
echo "This will:"
echo "  • Delete and recreate the root (@) btrfs subvolume"
echo "  • Reinstall base system packages from scratch"
echo "  • Clear all user ~/.config directories (auth keys preserved)"
echo "  • Preserve home directories, passwords, and FIDO2 login keys"
echo ""

# ── Required tools in live environment ──────────────────────────────────────
# WHY: The archiso live environment has minimal packages. We need these specific
#      tools that may not be pre-installed on the ISO:
#      - cryptsetup: to detect and unlock LUKS partitions
#      - btrfs-progs: to manage btrfs subvolumes
#      - jq: used by some sub-scripts
#      - libfido2: for FIDO2/token-based LUKS unlock
# -yd: sync database, download-only skip (don't install dependencies blindly)
pacman -Syd --noconfirm cryptsetup btrfs-progs jq libfido2

# ── Drive selection ──────────────────────────────────────────────────────────
# List all block devices so the user can see what's available
lsblk
echo ""
read -rp "Enter drive to reset (e.g., /dev/sda): " DRIVE

# Assume standard partition layout from arch-autoinstall.sh:
#   partition 1 = EFI/boot
#   partition 2 = root (possibly LUKS)
# NOTE: This assumes a non-NVMe drive. NVMe would need ${DRIVE}p2 etc.
ROOT_PART="${DRIVE}2"
EFI_PART="${DRIVE}1"

echo ""
echo "WARNING: The root subvolume on $ROOT_PART will be DELETED and reinstalled."
echo "         User home directories will be preserved."
echo "         App configs (~/.config) will be wiped (Yubico auth keys excepted)."
echo ""

# Require explicit "YES" (uppercase) to prevent accidental data loss
read -rp "Type YES to continue: " _CONFIRM
[[ "$_CONFIRM" == "YES" ]] || { echo "Aborted."; exit 1; }

# ── LUKS detection and unlock ────────────────────────────────────────────────
# Default assumption: root partition is NOT encrypted — MAPPER_DEV points to it directly.
# If LUKS is detected, MAPPER_DEV is updated to /dev/mapper/cryptroot after unlock.
MAPPER_DEV="$ROOT_PART"

# cryptsetup isLuks: exits 0 if the partition has a LUKS header, non-zero if not.
if cryptsetup isLuks "$ROOT_PART" 2>/dev/null; then
    echo ""
    echo "Partition $ROOT_PART is LUKS2-encrypted."
    echo "Select unlock method:"
    echo "  1) Try enrolled token (FIDO2/TPM2) first, fall back to passphrase  [recommended]"
    echo "  2) Passphrase only"
    echo "  3) Enrolled token only (FIDO2/TPM2)"
    read -rp "Choice [1]: " _UNLOCK
    _UNLOCK="${_UNLOCK:-1}"  # Default to option 1 if user just presses Enter

    case "$_UNLOCK" in
        1)
            # Best of both worlds: try hardware token first, fall back to passphrase.
            # This works whether or not the user has their FIDO2 key with them.
            echo "Insert FIDO2 key if using one, then press ENTER..."
            pause
            # --token-only: use enrolled tokens (FIDO2, TPM2) without prompting passphrase
            # If token unlock fails (key not present / not enrolled), fall back to password
            if ! cryptsetup open --token-only "$ROOT_PART" cryptroot 2>/dev/null; then
                echo "Token unlock failed — enter passphrase..."
                cryptsetup open "$ROOT_PART" cryptroot
            fi
            ;;
        2)
            # Traditional passphrase-only unlock — no hardware key needed
            cryptsetup open "$ROOT_PART" cryptroot
            ;;
        3)
            # Token-only mode — will fail if key isn't present/enrolled
            echo "Insert FIDO2 key and press ENTER..."
            pause
            cryptsetup open --token-only "$ROOT_PART" cryptroot
            ;;
        *)
            echo "Invalid choice, using passphrase..."
            cryptsetup open "$ROOT_PART" cryptroot
            ;;
    esac
    # After unlock, the decrypted device is available at /dev/mapper/cryptroot
    MAPPER_DEV="/dev/mapper/cryptroot"
    echo "Partition unlocked."
fi

# ── Detect installed kernel from EFI partition ───────────────────────────────
# WHY: We need to know which kernel to reinstall (linux, linux-lts, linux-zen, etc.).
# HOW: Mount the EFI partition temporarily and look for vmlinuz-* kernel images.
#      The filename suffix is the package name (e.g. vmlinuz-linux-lts → linux-lts).
TMPBOOT=$(mktemp -d /tmp/arch-reset-boot.XXXXXX)
mount "$EFI_PART" "$TMPBOOT"
KERNEL_PKG="linux"  # Default to mainline kernel
for _img in "$TMPBOOT"/vmlinuz-*; do
    # Loop over glob; first match wins (strip the vmlinuz- prefix)
    [[ -f "$_img" ]] && KERNEL_PKG=$(basename "$_img" | sed 's/^vmlinuz-//') && break
done
umount "$TMPBOOT"; rmdir "$TMPBOOT"
echo "Detected kernel: $KERNEL_PKG"

# ── Detect GPU from live hardware ────────────────────────────────────────────
# WHY: Different GPU vendors need different Xorg/DRM driver packages.
#      lspci reads the PCI device list; grep filters for display-related devices
#      (VGA=traditional GPU, 3D=compute GPU/integrated graphics).
# GPU_PKGS will be passed to pacstrap as an extra package.
GPU_INFO=$(lspci 2>/dev/null | grep -E "VGA|3D" || true)
GPU_PKGS=""
if   echo "$GPU_INFO" | grep -qi nvidia; then GPU_PKGS="nvidia-open"    # NVIDIA open-source driver
elif echo "$GPU_INFO" | grep -qi amd;    then GPU_PKGS="xf86-video-amdgpu"  # AMD open-source driver
elif echo "$GPU_INFO" | grep -qi intel;  then GPU_PKGS="xf86-video-intel"   # Intel driver
fi
# If no GPU detected, GPU_PKGS remains empty and the kernel's DRM will handle it

# ── Mount btrfs top-level ────────────────────────────────────────────────────
# WHY: We need to access the raw btrfs filesystem (not via a subvolume) so we
#      can manipulate subvolumes directly.
# HOW: subvolid=5 is always the btrfs top-level (root of all subvolumes).
BTRFS_MNT="$TMPDIR/btrfs"
mkdir -p "$BTRFS_MNT"
mount -o subvolid=5 "$MAPPER_DEV" "$BTRFS_MNT"

# ── Save critical configuration from @ ──────────────────────────────────────
# WHY: We're about to destroy the @ subvolume, which contains /etc.
#      We need to preserve critical system files so the reinstalled system
#      has the same users, passwords, locales, hostname, and boot config.
echo "Saving system configuration..."
SAVED="$TMPDIR/saved"

# _save: helper to copy a file/dir from the old @ subvolume to our save area.
# Skips silently if the source doesn't exist (not all systems have all configs).
_save() {
    local src="$BTRFS_MNT/@/etc/$1"
    local dst="$SAVED/etc/$1"
    [[ -e "$src" ]] || return 0  # Skip if source doesn't exist
    mkdir -p "$(dirname "$dst")"
    cp -a "$src" "$dst"  # -a: archive mode (preserves permissions, timestamps, symlinks)
}

# User and group databases — contain usernames, UIDs, group memberships
_save passwd
_save shadow    # Hashed passwords (mode 000 — only root can read)
_save group
_save gshadow   # Group passwords (mode 000)

# Privilege escalation config
_save sudoers
_save sudoers.d

# PAM (Pluggable Authentication Modules) — contains FIDO2 PAM config
# WHY: Must preserve this so FIDO2 login works after reset
_save pam.d

# System identity and locale
_save hostname
_save locale.conf
_save locale.gen
_save vconsole.conf   # Console keymap and font settings

# Boot configuration
_save fstab           # Mount point definitions (UUIDs etc.)
_save mkinitcpio.conf # Initramfs hooks config (encryption hooks must be preserved)
_save mkinitcpio.conf.d
_save default/grub    # GRUB cmdline (has cryptdevice= or rd.luks.name= parameters)

# Network connection profiles
_save NetworkManager  # Contains /etc/NetworkManager/system-connections/ WiFi passwords

# Save timezone symlink target as plain text because symlinks can't cross roots.
# The symlink /etc/localtime → /usr/share/zoneinfo/Europe/Vienna would be broken
# if we copied the symlink itself into a different root.
{ readlink "$BTRFS_MNT/@/etc/localtime" 2>/dev/null || echo "/usr/share/zoneinfo/UTC"; } \
    > "$SAVED/timezone"

# ── Clear ~/.config in @home (preserve auth-critical subdirs) ────────────────
# WHY: After a system reset, old app configs may be incompatible with the
#      freshly installed versions. Clearing them gives apps a clean start.
# EXCEPTION: Yubico/ and pam-u2f/ contain FIDO2/U2F PAM key files.
#      Deleting these would break FIDO2 login for all users after the reset.
echo "Clearing user app configs..."
PRESERVED_CONFIG_DIRS=("Yubico" "pam-u2f")

# Iterate over all user home directories in the @home subvolume
for _homedir in "$BTRFS_MNT/@home"/*/; do
    [[ -d "$_homedir" ]] || continue  # Skip if glob matched nothing
    _user=$(basename "$_homedir")
    _cfgdir="$_homedir/.config"
    [[ -d "$_cfgdir" ]] || continue   # Skip users with no .config
    echo "  Clearing ~/.config for: $_user"

    # Delete all top-level files in .config (not directories — handled separately)
    # -mindepth 1: don't delete .config itself
    # -maxdepth 1: only direct children
    # ! -type d: only files, not directories
    find "$_cfgdir" -mindepth 1 -maxdepth 1 ! -type d -delete

    # Delete subdirectories, except preserved ones
    # Using process substitution + read with NUL delimiter for paths with spaces
    while IFS= read -r -d '' _subdir; do
        _dname=$(basename "$_subdir")
        _skip=false
        # Check if this directory is in the preserved list
        for _keep in "${PRESERVED_CONFIG_DIRS[@]}"; do
            [[ "$_dname" == "$_keep" ]] && _skip=true && break
        done
        $_skip || rm -rf "$_subdir"  # Delete if not in preserved list
    done < <(find "$_cfgdir" -mindepth 1 -maxdepth 1 -type d -print0)
done

# ── Delete @ and recreate fresh ──────────────────────────────────────────────
# WHY: btrfs subvolume delete removes the entire @ filesystem tree instantly.
#      Creating a new empty @ gives us a clean slate for pacstrap.
echo "Deleting root subvolume @..."
btrfs subvolume delete "$BTRFS_MNT/@"
echo "Creating fresh root subvolume @..."
btrfs subvolume create "$BTRFS_MNT/@"

# ── Mount for installation ───────────────────────────────────────────────────
# Mount the new empty @ subvolume and the existing @home subvolume for pacstrap.
umount "$BTRFS_MNT"
mount -o subvol=@ "$MAPPER_DEV" /mnt
mkdir -p /mnt/home
mount -o subvol=@home "$MAPPER_DEV" /mnt/home
mkdir -p /mnt/boot
mount "$EFI_PART" /mnt/boot

# ── Pacstrap base system ─────────────────────────────────────────────────────
# WHY: pacstrap installs packages into the new root at /mnt.
#      We install only the minimal base needed to boot and manage the system.
#      The full package set is reinstalled via tui-install.sh after first boot.
echo "Reinstalling base system (this will take a while)..."
# shellcheck disable=SC2086 — GPU_PKGS is intentionally word-split here
pacstrap /mnt \
    base base-devel "$KERNEL_PKG" linux-firmware vim zsh git networkmanager grub efibootmgr \
    btrfs-progs cryptsetup libfido2 pam-u2f sudo less jq $GPU_PKGS

# ── Restore saved configuration ──────────────────────────────────────────────
# WHY: The fresh @ has default /etc files from pacstrap. We overwrite them
#      with the saved versions to restore users, passwords, hostname, etc.
echo "Restoring system configuration..."

# _restore: copy from save area into the new /mnt/etc
_restore() {
    local src="$SAVED/etc/$1"
    local dst="/mnt/etc/$1"
    [[ -e "$src" ]] || return 0  # Skip if we didn't save this file
    mkdir -p "$(dirname "$dst")"
    cp -a "$src" "$dst"
}

# Auth files get explicit permission modes with `install` rather than `cp -a`
# because cp -a preserves the original permissions which may be wrong after
# copying across filesystem boundaries.
# passwd/group: world-readable (644), shadow/gshadow: inaccessible to non-root (000)
[[ -f "$SAVED/etc/passwd"  ]] && install -m 644 "$SAVED/etc/passwd"  /mnt/etc/passwd
[[ -f "$SAVED/etc/group"   ]] && install -m 644 "$SAVED/etc/group"   /mnt/etc/group
[[ -f "$SAVED/etc/shadow"  ]] && install -m 000 "$SAVED/etc/shadow"  /mnt/etc/shadow
[[ -f "$SAVED/etc/gshadow" ]] && install -m 000 "$SAVED/etc/gshadow" /mnt/etc/gshadow

# Restore everything else using the _restore helper
_restore sudoers
_restore sudoers.d
_restore pam.d         # FIDO2 PAM rules restored here
_restore hostname
_restore locale.conf
_restore locale.gen
_restore vconsole.conf
_restore fstab         # CRITICAL: without fstab the system won't mount properly on boot
_restore mkinitcpio.conf      # CRITICAL: must have correct encryption hooks
_restore mkinitcpio.conf.d
_restore default/grub  # CRITICAL: must have correct cryptdevice= kernel parameters
_restore NetworkManager

# Restore the timezone symlink using the saved target path
TZ_TARGET=$(cat "$SAVED/timezone")
ln -sf "$TZ_TARGET" /mnt/etc/localtime

# ── Chroot: regenerate initramfs, GRUB menu, services ───────────────────────
# WHY: mkinitcpio and grub-mkconfig must run from inside the new root because
#      they reference files and kernel modules specific to that root.
#      Running them from outside would produce incorrect initramfs/GRUB configs.
echo "Finalizing inside chroot..."
arch-chroot /mnt /bin/bash <<'CHROOT_EOF'
set -euo pipefail

# Regenerate locale files (locale.gen was restored, re-run locale-gen to apply)
locale-gen

# Sync hardware clock from system time
hwclock --systohc

# Enable NetworkManager so the system has network on first boot
systemctl enable NetworkManager

# Regenerate initramfs for ALL installed kernels (-P = all presets)
# WHY: The restored mkinitcpio.conf has the correct hooks (encrypt, btrfs, etc.)
#      but the initramfs itself was in the old @ and is now gone.
mkinitcpio -P

# Regenerate GRUB boot menu
# WHY: GRUB needs a fresh grub.cfg pointing to the new kernel images.
#      The restored /etc/default/grub already has the correct cmdline.
grub-mkconfig -o /boot/grub/grub.cfg

# Fix home directory ownership using the restored UID/GID from /etc/passwd
# WHY: @home was preserved but the new @ has a fresh uid/gid namespace until
#      /etc/passwd is restored. Now that passwd is in place, we re-apply
#      ownership to ensure home dirs are accessible by the right users.
# IFS=: splits /etc/passwd fields by colon; fields: name:pass:uid:gid:gecos:home:shell
while IFS=: read -r _uname _ _uid _gid _ _home _; do
    # Only fix user accounts (UID >= 1000), not system accounts
    (( _uid >= 1000 )) && [[ -d "$_home" ]] && chown -R "${_uid}:${_gid}" "$_home" || true
done < /etc/passwd
CHROOT_EOF

echo ""
echo "======================================="
echo "  Reset complete!"
echo "======================================="
echo "  umount -R /mnt && reboot"
