#!/bin/bash
# ============================================================
#  fws-windows-deploy — déploie Windows 11 en OFFLINE-APPLY sur un disque dédié,
#  depuis le live FWS (appelé en %post --nochroot, cible sous /mnt/sysroot).
#
#  Windows Setup ne tourne JAMAIS : on carve les partitions, on applique
#  install.wim (wimlib), on rend bootable (BCD), on injecte l'answer file offline
#  + le composant gameboot, on provisionne l'hibernation FWS.
#
#  SÉCURITÉ (risque n°1 = effacer le mauvais NVMe) : garde-fous FAIL-CLOSED —
#  état PARSÉ (jamais sourcé → pas d'injection), disque cible VALIDÉ contre la
#  liste des disques réels, comparé à TOUS les disques protégés (racine FWS
#  multi-PV incluse, ESP, média ISO, support live), aucun label FWS résiduel
#  ailleurs, adressage des partitions par NUMÉRO sur le disque cible (jamais par
#  by-partlabel global) avec vérif du parent. Le moindre doute → abort. Échec →
#  DEPLOY_FAILED, aucune entrée UEFI piégeuse, FWS reste seul bootable.
#
#  ⚠ NON VALIDÉ SUR MATÉRIEL RÉEL (déploiement Windows réel + BCD).
# ============================================================
set -u

STATE=/tmp/fws-windows
SYSROOT=/mnt/sysroot
while [ $# -gt 0 ]; do
    case "$1" in
        --state)   STATE="${2:-}"; shift 2 ;;
        --sysroot) SYSROOT="${2:-}"; shift 2 ;;
        *) shift ;;
    esac
done

log(){ printf '\e[36m[win-deploy]\e[0m %s\n' "$*"; }
warn(){ printf '\e[33m[win-deploy] %s\e[0m\n' "$*" >&2; }
status(){ mkdir -p "$SYSROOT/var/lib/fws" 2>/dev/null; echo "$1" > "$SYSROOT/var/lib/fws/windows-deploy.status" 2>/dev/null || true; }
die(){ printf '\e[31m[win-deploy] ABORT : %s\e[0m\n' "$*" >&2; status "DEPLOY_FAILED: $*"; exit 1; }
canon(){ readlink -f "$1" 2>/dev/null || printf '%s' "$1"; }
# Device de la partition N sur le disque D (nvme/mmc → pN ; sata → N).
part_dev(){ case "$1" in *[0-9]) printf '%sp%s' "$1" "$2";; *) printf '%s%s' "$1" "$2";; esac; }

# --- 0) État du spoke : PARSÉ en clé=valeur littéral (JAMAIS sourcé) [M3] -----
[ -r "$STATE" ] || { log "pas de $STATE → dual-boot non demandé, rien à faire."; exit 0; }
enabled=0; disk=""; size_gib=0; iso=""; edition=""; confirm_erase=0; intel_vmd=0
while IFS='=' read -r k v; do
    case "$k" in
        enabled)       enabled="$v" ;;
        disk)          disk="$v" ;;
        size_gib)      size_gib="$v" ;;
        iso)           iso="$v" ;;
        edition)       edition="$v" ;;
        confirm_erase) confirm_erase="$v" ;;
        intel_vmd)     intel_vmd="$v" ;;
    esac
done < "$STATE"
[ "${enabled:-0}" = 1 ] || { log "dual-boot désactivé, rien à faire."; exit 0; }
[ "${size_gib:-0}" -eq "${size_gib:-0}" ] 2>/dev/null || die "taille non numérique"
log "Dual-boot Windows demandé : disque=$disk taille=${size_gib}Gio iso=$iso"

# --- Résolveur : TOUS les disques physiques sous un montage (fail-closed) [M1]
fws_disks_of(){
    local src
    src="$(findmnt -no SOURCE "$1" 2>/dev/null)" || return 1
    [ -n "$src" ] || return 1
    lsblk -spno NAME,TYPE "$src" 2>/dev/null | awk '$2=="disk"{print $1}'
}

# --- 1) Garde-fous AVANT toute écriture (fail-closed) -----------------------
WIN_DISK="$disk"
[ -b "$WIN_DISK" ] || die "cible '$WIN_DISK' n'est pas un disque bloc"
# [M3] 'disk' DOIT être un vrai disque physique (neutralise toute valeur piégée).
lsblk -dpno NAME,TYPE 2>/dev/null | awk '$2=="disk"{print $1}' | grep -qxF "$WIN_DISK" \
    || die "cible '$WIN_DISK' absente de la liste des disques physiques"
findmnt -no SOURCE "$SYSROOT" >/dev/null 2>&1 || die "$SYSROOT non monté"
[ -f "$iso" ] || die "ISO introuvable : $iso"
[ "${size_gib:-0}" -ge 100 ] || die "taille < 100 Gio"

# Swap d'hibernation (RAM+VRAM+marge, plancher 96) — calculé ICI pour le garde taille [M7].
MEM=$(awk '/^MemTotal/{print int($2/1024)}' /proc/meminfo)
VRAM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | awk '{s+=$1}END{print s+0}')
SWAP_GIB=$(( (MEM + VRAM + 4096 + 1023) / 1024 )); [ "$SWAP_GIB" -lt 96 ] && SWAP_GIB=96

# Le disque cible ne doit être AUCUN disque protégé. On sonde uniquement de VRAIS
# montages [m1], et on compare à TOUS les disques porteurs [M1]. Resolver muet → abort.
WIN_C="$(canon "$WIN_DISK")"
for probe in "$SYSROOT" "$SYSROOT/boot/efi" "$SYSROOT/boot" /run/archiso/bootmnt; do
    findmnt -M "$probe" >/dev/null 2>&1 || continue
    mapfile -t pds < <(fws_disks_of "$probe")
    [ "${#pds[@]}" -gt 0 ] || die "resolver muet sur '$probe' → abort (fail-closed)"
    for d in "${pds[@]}"; do
        [ "$WIN_C" != "$(canon "$d")" ] || die "$WIN_DISK == disque protégé ($probe via $d) — refus d'effacer"
    done
done
# L'ISO ne doit pas vivre sur la cible — fail-closed si le média est indéterminable [m2].
iso_mnt="$(df --output=target "$iso" 2>/dev/null | tail -1)"
[ -n "$iso_mnt" ] || die "média de l'ISO indéterminable → abort (fail-closed)"
mapfile -t isods < <(fws_disks_of "$iso_mnt")
[ "${#isods[@]}" -gt 0 ] || die "resolver muet sur le média ISO → abort"
for d in "${isods[@]}"; do
    [ "$WIN_C" != "$(canon "$d")" ] || die "l'ISO vit sur la cible $WIN_DISK — refus"
done
# Taille : NTFS + ESP + MSR + swap doivent tenir [M7].
DGIB=$(( $(lsblk -dbno SIZE "$WIN_DISK" 2>/dev/null || echo 0) / 1073741824 ))
[ "$DGIB" -gt 0 ] && [ "$size_gib" -le $(( DGIB - SWAP_GIB - 4 )) ] \
    || die "taille ${size_gib} + swap ${SWAP_GIB} + réserve dépasse le disque ${DGIB} Gio"

# [B1] Aucun label FWS résiduel ne doit exister sur un AUTRE disque (collision
# by-partlabel = risque d'écrire ailleurs). On refuse plutôt que de deviner.
while read -r dev lbl; do
    case "$lbl" in
        FWSWIN|WINESP|FWSSWAP)
            pk="/dev/$(lsblk -no PKNAME "$dev" 2>/dev/null | head -1)"
            [ "$(canon "$pk")" = "$WIN_C" ] \
                || die "label '$lbl' déjà présent sur $pk (≠ cible) — nettoie-le d'abord (wipefs -a $pk)" ;;
    esac
done < <(lsblk -lnpo NAME,PARTLABEL 2>/dev/null)
log "Garde-fous OK : $WIN_DISK ($DGIB Gio) sûr à dédier à Windows (swap ${SWAP_GIB} Gio)."

# --- 2) Neutraliser toute activation du disque cible [M2] -------------------
log "Neutralisation des partitions de $WIN_DISK…"
for part in $(lsblk -lnpo NAME "$WIN_DISK" 2>/dev/null | tail -n +2); do
    umount "$part" 2>/dev/null || true
    swapoff "$part" 2>/dev/null || true
    for vg in $(pvs --noheadings -o vg_name "$part" 2>/dev/null); do vgchange -an "$vg" 2>/dev/null || true; done
    for h in $(lsblk -lno NAME "$part" 2>/dev/null | tail -n +2); do
        case "$(lsblk -no TYPE "/dev/$h" 2>/dev/null | head -1)" in
            raid*) mdadm --stop "/dev/$h" 2>/dev/null || true ;;
            crypt) cryptsetup close "$h" 2>/dev/null || true ;;
        esac
    done
done
# Arrays md dont le disque cible est lui-même membre (détection par colonne TYPE).
for md in $(lsblk -lnpo NAME,TYPE "$WIN_DISK" 2>/dev/null | awk '$2 ~ /raid/{print $1}'); do
    mdadm --stop "$md" 2>/dev/null || true
done
# Re-vérif fail-closed : plus rien d'actif sur le disque cible.
for part in $(lsblk -lnpo NAME "$WIN_DISK" 2>/dev/null | tail -n +2); do
    findmnt -S "$part" >/dev/null 2>&1 && die "$part encore monté après neutralisation"
    swapon --show=NAME --noheadings 2>/dev/null | grep -qx "$part" && die "$part encore en swap"
    [ -n "$(lsblk -lno NAME "$part" 2>/dev/null | tail -n +2)" ] && die "$part a encore des holders (md/dm) actifs"
done

# --- 3) Carve (mode DEDICATE) — adressage par NUMÉRO sur le disque cible [B1]
log "Carve : ESP 1Gio + MSR 16Mio + NTFS ${size_gib}Gio + swap ${SWAP_GIB}Gio"
sgdisk --zap-all "$WIN_DISK"                                    || die "sgdisk --zap-all a échoué"
sgdisk -n1:0:+1GiB           -t1:ef00 -c1:WINESP  "$WIN_DISK"   || die "création WINESP"
sgdisk -n2:0:+16MiB          -t2:0c01 -c2:MSR     "$WIN_DISK"   || warn "MSR non créée (non bloquant)"
sgdisk -n3:0:+${size_gib}GiB -t3:0700 -c3:FWSWIN  "$WIN_DISK"   || die "création partition Windows"
sgdisk -n4:0:+${SWAP_GIB}GiB -t4:8200 -c4:FWSSWAP "$WIN_DISK"   || die "création swap"
partprobe "$WIN_DISK"; udevadm settle
ESP_PART="$(part_dev "$WIN_DISK" 1)"
WIN_PART="$(part_dev "$WIN_DISK" 3)"
SWAP_PART="$(part_dev "$WIN_DISK" 4)"
for p in "$ESP_PART" "$WIN_PART" "$SWAP_PART"; do
    [ -b "$p" ] || die "partition $p absente après carve"
    pk="/dev/$(lsblk -no PKNAME "$p" 2>/dev/null | head -1)"
    [ "$(canon "$pk")" = "$WIN_C" ] || die "$p n'est PAS sur $WIN_DISK — abort"
done
mkfs.fat -F32 -n WINESP "$ESP_PART" || die "mkfs.fat WINESP"
mkswap  -L FWSSWAP "$SWAP_PART"      || die "mkswap FWSSWAP"
mkntfs -Q -f -L FWSWIN "$WIN_PART" || mkntfs -f -L FWSWIN "$WIN_PART" || die "mkntfs FWSWIN"

# --- 4) Appliquer install.wim (wimlib) sur la NTFS --------------------------
ISO_MNT="$(mktemp -d)"; WIN_MNT="$(mktemp -d)"; ESP_MNT="$(mktemp -d)"
cleanup(){ for m in "$WIN_MNT" "$ESP_MNT" "$ISO_MNT"; do umount "$m" 2>/dev/null || true; done; }
trap cleanup EXIT
mount -o loop,ro "$iso" "$ISO_MNT" || die "montage ISO échoué"
IMG="$ISO_MNT/sources/install.wim"; [ -f "$IMG" ] || IMG="$ISO_MNT/sources/install.esd"
[ -f "$IMG" ] || die "install.wim/.esd introuvable dans l'ISO"
# Édition par NOM (insensible à la casse) ; repli image unique [m4].
WANT="${edition:-Windows 11 Pro}"
IDX="$(wimlib-imagex info "$IMG" 2>/dev/null | awk -v w="$(printf '%s' "$WANT" | tr 'A-Z' 'a-z')" -F': *' '
    tolower($1) ~ /^index/{i=$2} index(tolower($0), w){print i; exit}')"
if [ -z "$IDX" ]; then
    CNT="$(wimlib-imagex info "$IMG" 2>/dev/null | awk -F': *' 'tolower($1) ~ /image count/{print $2; exit}')"
    [ "${CNT:-0}" = 1 ] && IDX=1
fi
[ -n "$IDX" ] || die "édition « $WANT » absente de l'ISO (wimlib-imagex info) — ISO Home/N/tiny ?"
log "Application de l'image (index $IDX = $WANT) sur $WIN_PART — long…"
wimlib-imagex apply "$IMG" "$IDX" "$WIN_PART" || die "wimlib-imagex apply a échoué"

# --- 5) Bootabilité : BCD (hivex → repli WinPE-bcdboot) ---------------------
mount "$WIN_PART" "$WIN_MNT" || die "montage NTFS échoué"
mount "$ESP_PART" "$ESP_MNT" || die "montage WINESP échoué"
mkdir -p "$ESP_MNT/EFI/Microsoft/Boot"
cp -rn "$WIN_MNT/Windows/Boot/EFI/." "$ESP_MNT/EFI/Microsoft/Boot/" 2>/dev/null || true
WIN_PARTUUID="$(blkid -s PARTUUID -o value "$WIN_PART" 2>/dev/null)"
DISK_GUID="$(sgdisk -p "$WIN_DISK" 2>/dev/null | sed -n 's/^Disk identifier (GUID): *//p')"
# Pilote Intel VMD à injecter ? (case cochée dans le spoke + .inf présents).
DRV=""
if [ "${intel_vmd:-0}" = 1 ]; then
    if ls /usr/local/share/fws/drivers/intel-vmd/*.inf >/dev/null 2>&1; then
        DRV=/usr/local/share/fws/drivers/intel-vmd
        log "Intel VMD demandé + pilote présent → injection via WinPE (dism)."
    else
        warn "Intel VMD demandé mais AUCUN pilote dans .../drivers/intel-vmd/ — Windows tentera son pilote VMD inbox (25H2). Dépose le pilote Intel si le boot échoue."
    fi
fi
# Avec un pilote VMD à injecter, on FORCE la voie WinPE (dism y tourne). Sinon
# hivex (qui bascule de toute façon sur WinPE tant que non validé).
if [ -z "$DRV" ] && /usr/local/lib/fws/fws-bcd-write "$ESP_MNT" "$WIN_MNT" "$WIN_PARTUUID" "$DISK_GUID"; then
    log "BCD écrit (voie hivex)."
    BCDFIX=0
else
    [ -n "$DRV" ] && log "Bascule WinPE (bcdboot + injection pilote VMD)." \
        || warn "Voie hivex non validée → repli WinPE-bcdboot (1 reboot auto)."
    umount "$WIN_MNT" 2>/dev/null || true
    /usr/local/bin/fws-windows-bcdfix "$ISO_MNT" "$ESP_MNT" "$WIN_DISK" 1 "$DRV" \
        || die "repli WinPE-bcdfix a échoué — Windows non bootable (FWS reste seul bootable)"
    BCDFIX=1
    mount "$WIN_PART" "$WIN_MNT" 2>/dev/null || die "remontage NTFS échoué"
fi

# --- 6) Anti-BitLocker HORS LIGNE (crucial : évite le lockout) --------------
HIVE="$WIN_MNT/Windows/System32/config/SYSTEM"
if command -v hivexget >/dev/null 2>&1 && [ -f "$HIVE" ]; then
    SEL="$(hivexget "$HIVE" Select Current 2>/dev/null | tr -cd '0-9')"; [ -n "$SEL" ] || SEL=1
    printf '[HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet%03d\\Control\\BitLocker]\n"PreventDeviceEncryption"=dword:00000001\n' "$SEL" \
        | hivexregedit --merge --prefix 'HKEY_LOCAL_MACHINE\SYSTEM' "$HIVE" \
        && log "Anti-BitLocker offline posé (ControlSet$(printf '%03d' "$SEL"))." \
        || warn "écriture anti-BitLocker offline échouée (ceinture reg add en specialize)."
else
    warn "hivex/SYSTEM absent → anti-BitLocker via la ceinture specialize seule."
fi

# --- 7) Answer file offline + injection du composant gameboot ---------------
TMPL=/usr/local/share/fws/gameboot-windows/unattend.offline.xml.tmpl
HN="$(tr -cd 'a-zA-Z0-9-' < /tmp/fws-hostname 2>/dev/null)"; [ -n "$HN" ] || HN=fws-win
LOC="$(. "$SYSROOT/etc/locale.conf" 2>/dev/null; printf '%s' "${LANG%%.*}")"; [ -n "$LOC" ] || LOC=fr-CH
LOC="${LOC/_/-}"
FIRSTBOOT='C:\ProgramData\FWS\deploy\FirstBoot-FwsGameboot.ps1'
mkdir -p "$WIN_MNT/Windows/Panther" "$WIN_MNT/ProgramData/FWS/deploy"
sed -e "s|__HOSTNAME__|$HN|g" -e "s|__USER__|gaming|g" -e "s|__DISPLAY__|Gaming|g" \
    -e "s|__LOCALE__|$LOC|g" -e "s|__INPUT__|$LOC|g" -e "s|__FIRSTBOOT__|${FIRSTBOOT//\\/\\\\}|g" \
    "$TMPL" > "$WIN_MNT/Windows/Panther/unattend.xml" || die "rendu unattend.xml échoué"
cp -rf /usr/local/share/fws/gameboot-windows/. "$WIN_MNT/ProgramData/FWS/deploy/" || die "injection gameboot échouée"
rm -f "$WIN_MNT/ProgramData/FWS/deploy/unattend.offline.xml.tmpl" 2>/dev/null || true
log "unattend.xml + composant gameboot injectés sur la NTFS."

# --- 8) Vérification AVANT toute écriture NVRAM -----------------------------
[ -f "$WIN_MNT/Windows/System32/ntoskrnl.exe" ]   || die "ntoskrnl.exe absent → apply incomplet"
[ -f "$WIN_MNT/Windows/System32/winload.efi" ]    || die "winload.efi absent"
[ -f "$ESP_MNT/EFI/Microsoft/Boot/bootmgfw.efi" ] || die "bootmgfw.efi absent de l'ESP"
[ "$BCDFIX" = 1 ] || [ -s "$ESP_MNT/EFI/Microsoft/Boot/BCD" ] || die "BCD absent/vide (voie hivex)"
sync

# --- 9) Entrées de boot : FWS TOUJOURS en tête [M5][M6] ---------------------
# Voie hivex directe : on crée l'entrée Windows persistante (en queue).
if [ "$BCDFIX" = 0 ]; then
    efibootmgr -c -d "$WIN_DISK" -p 1 -L 'Windows Boot Manager' -l '\EFI\Microsoft\Boot\bootmgfw.efi' >/dev/null 2>&1 || warn "création entrée UEFI Windows échouée"
    WBN="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *Windows Boot Manager.*/\1/p' | head -1)"
    [ -n "$WBN" ] && sed -i "s/^FWS_WINDOWS_BOOTNUM=.*/FWS_WINDOWS_BOOTNUM=\"$WBN\"/" \
        "$SYSROOT/etc/fws/gameboot.conf" 2>/dev/null || true
fi
# Dans TOUS les cas : FWS en BootOrder[0] (Windows/WinPE jamais 1er). Si aucune
# entrée NVRAM FWS (booté via \EFI\BOOT\BOOTX64.EFI removable), en créer une.
FBN="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *\(FWS\|GRUB\).*/\1/p' | head -1)"
if [ -z "$FBN" ]; then
    esp_src="$(findmnt -no SOURCE "$SYSROOT/boot/efi" 2>/dev/null)"
    if [ -b "$esp_src" ]; then
        sdisk="/dev/$(lsblk -no PKNAME "$esp_src" 2>/dev/null | head -1)"
        spn="$(lsblk -no PARTN "$esp_src" 2>/dev/null | head -1)"
        efibootmgr -c -d "$sdisk" -p "${spn:-1}" -L 'FWS' -l '\EFI\BOOT\BOOTX64.EFI' >/dev/null 2>&1 || true
        FBN="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *FWS.*/\1/p' | head -1)"
    fi
fi
order="$(efibootmgr 2>/dev/null | sed -n 's/^BootOrder: //p' | tr -d '[:space:]')"
if [ -n "$FBN" ] && [ -n "$order" ]; then
    rest="$(printf '%s' "$order" | tr ',' '\n' | grep -vx "$FBN" | paste -sd, -)"
    efibootmgr -o "${FBN}${rest:+,$rest}" >/dev/null 2>&1 || true   # FWS 1er, reste (Windows/WinPE) derrière
fi

# --- 10) Hibernation FWS (chroot cible) : swap dédié → resume= --------------
SWAP_UUID="$(blkid -s UUID -o value "$SWAP_PART" 2>/dev/null)"
if [ -n "$SWAP_UUID" ] && command -v arch-chroot >/dev/null 2>&1; then
    log "Config hibernation FWS (resume=UUID=$SWAP_UUID)…"
    cat > "$SYSROOT/etc/fws-gameboot-swap.sh" <<CHROOT
set -u
grep -q "UUID=$SWAP_UUID" /etc/fstab 2>/dev/null || printf 'UUID=%s none swap defaults 0 0\n' "$SWAP_UUID" >> /etc/fstab
grep -qE '^HOOKS=.*\bresume\b' /etc/mkinitcpio.conf || sed -i -E 's/^(HOOKS=\(.*)\bfilesystems\b/\1resume filesystems/' /etc/mkinitcpio.conf
mkinitcpio -P || true
if grep -q '^GRUB_CMDLINE_LINUX_DEFAULT=' /etc/default/grub; then
  grep -q 'resume=UUID=$SWAP_UUID' /etc/default/grub || sed -i 's|^GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"|GRUB_CMDLINE_LINUX_DEFAULT="\1 resume=UUID=$SWAP_UUID hibernate.compressor=lz4"|' /etc/default/grub
fi
grep -q '^GRUB_DISABLE_OS_PROBER=' /etc/default/grub || echo 'GRUB_DISABLE_OS_PROBER=true' >> /etc/default/grub
grub-mkconfig -o /boot/grub/grub.cfg || true
install -Dm644 /dev/stdin /etc/modprobe.d/nvidia-hibernate.conf <<'NV'
options nvidia NVreg_PreserveVideoMemoryAllocations=1 NVreg_TemporaryFilePath=/var/tmp
NV
systemctl enable nvidia-suspend.service nvidia-hibernate.service nvidia-resume.service 2>/dev/null || true
systemctl enable fws-gameboot-bootfix.service 2>/dev/null || true
CHROOT
    arch-chroot "$SYSROOT" bash /etc/fws-gameboot-swap.sh 2>&1 | tee -a /tmp/fws-windows-deploy.log || warn "config hibernation chroot partielle"
    rm -f "$SYSROOT/etc/fws-gameboot-swap.sh"
else
    warn "swap UUID introuvable ou arch-chroot absent → hibernation non configurée (reboot simple restera dispo)."
fi

sync
status "DEPLOY_OK: Windows sur $WIN_DISK (${size_gib}Gio) — SB à activer au firmware"
log "Déploiement Windows terminé.$([ "$BCDFIX" = 1 ] && echo ' (BCD via WinPE au 1er reboot)')"
log "⚠ Reste manuel : activer Secure Boot au firmware (sbctl verify déjà clean)."
exit 0
