#!/usr/bin/env bash
# End-to-end regression test for nomarchy-install: build the live ISO,
# run a fully OFFLINE unattended install into a fresh VM disk, then boot
# the installed system and capture what the first boot looks like.
#
# This automates the verification protocol from docs/TESTING.md §4 that
# debugged the installer in the first place. It asserts the machine-
# checkable parts (installer reaches poweroff; installed disk boots);
# the visual part — a fully themed desktop, no autogenerated-config
# banner — lands as screenshots for a human (or agent) to inspect:
#
#   $NOMARCHY_VM_DIR/install-typed.png  the typed command, pre-enter
#   $NOMARCHY_VM_DIR/luks-prompt.png    initrd passphrase prompt
#   $NOMARCHY_VM_DIR/first-boot.png     THE result: themed desktop
#
# Requirements: KVM, qemu, OVMF, dosfstools (mkfs.vfat), mtools (mcopy),
# python3. Tunables (env):
#   NOMARCHY_VM_DIR      scratch + screenshots   (default /tmp/nomarchy-vm)
#   NOMARCHY_TARGET_DIR  where the 25G target.img lives — must NOT be
#                        tmpfs (default ~/.cache/nomarchy-vm)
#   NOMARCHY_BOOT_WAIT   seconds to wait for the live desktop (default 480)
#   NOMARCHY_INSTALL_TIMEOUT  seconds before declaring a hang (default 2700)
#
# Unattended matrix (BACKLOG #74) — env flags only, no interactive options:
#   NOMARCHY_TEST_SWAP_GB=2          # default; 0 = no swap subvol
#   NOMARCHY_TEST_NO_LUKS=0          # default LUKS+passphrase; 1 = cleartext
# Matrix to run when checking swap/LUKS contracts:
#   (default)     SWAP=2  NO_LUKS=0
#   no-swap       SWAP=0  NO_LUKS=0
#   no-LUKS       SWAP=2  NO_LUKS=1
#   no-swap+no-LUKS  SWAP=0 NO_LUKS=1

set -euo pipefail
cd "$(dirname "$0")/.."

VM_DIR="${NOMARCHY_VM_DIR:-/tmp/nomarchy-vm}"
TARGET_DIR="${NOMARCHY_TARGET_DIR:-$HOME/.cache/nomarchy-vm}"
BOOT_WAIT="${NOMARCHY_BOOT_WAIT:-480}"
INSTALL_TIMEOUT="${NOMARCHY_INSTALL_TIMEOUT:-2700}"
TEST_SWAP_GB="${NOMARCHY_TEST_SWAP_GB:-2}"
TEST_NO_LUKS="${NOMARCHY_TEST_NO_LUKS:-0}"
[[ "$TEST_SWAP_GB" =~ ^[0-9]+$ ]] || { echo "NOMARCHY_TEST_SWAP_GB must be a whole number" >&2; exit 1; }
[[ "$TEST_NO_LUKS" == "0" || "$TEST_NO_LUKS" == "1" ]] || {
    echo "NOMARCHY_TEST_NO_LUKS must be 0 or 1" >&2; exit 1
}
export NOMARCHY_QMP_SOCK="$VM_DIR/qmp.sock"
QMP=tools/vm/qmp.py
SHOT=tools/vm/vncshot.py
VNC_DISPLAY=17 # port 5917

for tool in qemu-system-x86_64 mkfs.vfat mcopy python3; do
    command -v "$tool" >/dev/null || {
        echo "missing $tool — try: nix shell nixpkgs#qemu nixpkgs#dosfstools nixpkgs#mtools" >&2
        exit 1
    }
done
[ -r /dev/kvm ] || { echo "no KVM — this test is too slow without it" >&2; exit 1; }

mkdir -p "$VM_DIR" "$TARGET_DIR"
if df --output=fstype "$TARGET_DIR" | tail -1 | grep -q tmpfs; then
    echo "NOMARCHY_TARGET_DIR=$TARGET_DIR is tmpfs — the install writes ~10G; pick a real disk" >&2
    exit 1
fi

OVMF="${NOMARCHY_OVMF:-}"
if [ -z "$OVMF" ]; then
    for c in /run/current-system/sw/share/qemu/edk2-x86_64-code.fd \
             /run/current-system/sw/share/OVMF/OVMF_CODE.fd \
             /usr/share/OVMF/OVMF_CODE.fd; do
        [ -f "$c" ] && { OVMF="$c"; break; }
    done
fi
# Fall back to nixpkgs OVMF.fd (FV/OVMF_CODE.fd) when not on the host profile.
if [ -z "$OVMF" ] || [ ! -f "$OVMF" ]; then
    ovmf_store=$(nix build --no-link --print-out-paths nixpkgs#OVMF.fd 2>/dev/null || true)
    for c in "$ovmf_store"/FV/OVMF_CODE.fd "$ovmf_store"/FV/OVMF.fd; do
        [ -f "$c" ] && { OVMF="$c"; break; }
    done
fi
[ -n "$OVMF" ] && [ -f "$OVMF" ] || { echo "no OVMF firmware found (set NOMARCHY_OVMF=…)" >&2; exit 1; }

echo "==> Building the live ISO..."
nix build .#nixosConfigurations.nomarchy-live.config.system.build.isoImage
ISO=$(ls -1 result/iso/*.iso | head -n1)

echo "==> Preparing disks..."
echo "    matrix: SWAP_GB=$TEST_SWAP_GB NO_LUKS=$TEST_NO_LUKS"
# Unattended config rides in on a tiny vfat disk: the installer test
# can't reliably TYPE a 200-char env line into the guest (keystrokes
# get dropped), but one short mount-and-run command works.
{
    echo '#!/usr/bin/env bash'
    echo 'export NOMARCHY_UNATTENDED=1'
    echo 'export NOMARCHY_DISK=/dev/vda'
    echo 'export NOMARCHY_USERNAME=ada'
    echo 'export NOMARCHY_PASSWORD=nomarchy'
    echo 'export NOMARCHY_HOSTNAME=testbox'
    echo 'export NOMARCHY_KB_LAYOUT=us'
    echo 'export NOMARCHY_KB_VARIANT="(none)"'
    echo "export NOMARCHY_SWAP_GB=$TEST_SWAP_GB"
    echo 'export NOMARCHY_FINISH=poweroff'
    if [[ "$TEST_NO_LUKS" == "1" ]]; then
        echo 'export NOMARCHY_NO_LUKS=1'
        echo 'unset NOMARCHY_LUKS_PASSPHRASE || true'
    else
        echo 'export NOMARCHY_LUKS_PASSPHRASE=testtest1'
    fi
    echo 'nomarchy-install 2>&1 | tee /tmp/install.log'
} > "$VM_DIR/go.sh"
chmod +x "$VM_DIR/go.sh"
rm -f "$VM_DIR/config.img"
truncate -s 8M "$VM_DIR/config.img"
mkfs.vfat "$VM_DIR/config.img" >/dev/null
mcopy -i "$VM_DIR/config.img" "$VM_DIR/go.sh" ::go.sh

rm -f "$TARGET_DIR/target.img" "$NOMARCHY_QMP_SOCK"
qemu-img create -f raw "$TARGET_DIR/target.img" 25G >/dev/null

qemu_base=(
    qemu-system-x86_64
    -enable-kvm -cpu host -m 6144 -smp 2
    -drive "if=pflash,format=raw,readonly=on,file=$OVMF"
    -device virtio-vga-gl -display egl-headless
    -vnc "127.0.0.1:$VNC_DISPLAY"
    -device virtio-net-pci,netdev=n0 -netdev user,id=n0,restrict=on # OFFLINE
    -qmp "unix:$NOMARCHY_QMP_SOCK,server,nowait"
)

echo "==> Phase 1: installing (offline) — this takes ~20-30 min..."
"${qemu_base[@]}" \
    -drive "file=$TARGET_DIR/target.img,if=virtio,format=raw" \
    -drive "file=$VM_DIR/config.img,if=virtio,format=raw,readonly=on" \
    -cdrom "$ISO" -boot d \
    >"$VM_DIR/qemu-install.log" 2>&1 &
QPID=$!
trap 'kill $QPID 2>/dev/null || true' EXIT

until [ -S "$NOMARCHY_QMP_SOCK" ]; do sleep 2; done
echo "    waiting ${BOOT_WAIT}s for the live desktop..."
sleep "$BOOT_WAIT"
python3 "$QMP" key meta_l-ret
sleep 8
python3 "$QMP" type 'sudo bash -c "mkdir -p /m; mount /dev/vdb /m; bash /m/go.sh"'
sleep 2
python3 "$SHOT" "$VM_DIR/install-typed.png" 127.0.0.1 "59$VNC_DISPLAY"
python3 "$QMP" key ret
echo "    install running (typed command captured in install-typed.png)..."

waited=0
while kill -0 $QPID 2>/dev/null; do
    if (( waited >= INSTALL_TIMEOUT )); then
        python3 "$SHOT" "$VM_DIR/install-hung.png" 127.0.0.1 "59$VNC_DISPLAY" || true
        echo "FAIL: installer did not power off within ${INSTALL_TIMEOUT}s (see install-hung.png)" >&2
        exit 1
    fi
    sleep 20; waited=$((waited + 20))
done
trap - EXIT
echo "    VM powered off after ~${waited}s — install completed."

echo "==> Phase 2: first boot of the installed system..."
rm -f "$NOMARCHY_QMP_SOCK"
"${qemu_base[@]}" \
    -drive "file=$TARGET_DIR/target.img,if=virtio,format=raw" \
    >"$VM_DIR/qemu-boot.log" 2>&1 &
QPID=$!
trap 'kill $QPID 2>/dev/null || true' EXIT

until [ -S "$NOMARCHY_QMP_SOCK" ]; do sleep 2; done
sleep 40
if [[ "$TEST_NO_LUKS" == "0" ]]; then
    python3 "$SHOT" "$VM_DIR/luks-prompt.png" 127.0.0.1 "59$VNC_DISPLAY"
    python3 "$QMP" type 'testtest1'
    python3 "$QMP" key ret
    sleep 150
else
    # Cleartext disk — no LUKS prompt; give the session time to come up.
    python3 "$SHOT" "$VM_DIR/boot-noluks.png" 127.0.0.1 "59$VNC_DISPLAY" || true
    sleep 150
fi
python3 "$SHOT" "$VM_DIR/first-boot.png" 127.0.0.1 "59$VNC_DISPLAY"
python3 "$QMP" quit || true
trap - EXIT

echo
echo "PASS (machine-checkable part): install powered off, installed disk boots."
echo "  matrix: SWAP_GB=$TEST_SWAP_GB NO_LUKS=$TEST_NO_LUKS"
echo "Now INSPECT $VM_DIR/first-boot.png:"
echo "  expected: themed desktop (wallpaper + waybar), NO red banner, no login prompt"
echo "  if it shows the Hyprland water-drop or an 'autogenerated config' banner,"
echo "  log in on tty2 (ada/nomarchy) and read /var/log/nomarchy-hm-preactivate.log"
