#!/bin/bash
# ============================================================================
# vm -- disposable Arch VM sandbox.
#
# Runs destructive system scripts (shutdown installers, chattr +i guards,
# pacman hooks) against a throwaway Arch guest, and verifies the outcome from
# the HOST -- a guest that powers itself off cannot report its own success.
# ============================================================================

set -euo pipefail

VMBOX_BIN_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd -P)"
readonly VMBOX_BIN_DIR
# shellcheck source=../lib/common.sh
source "$VMBOX_BIN_DIR/../lib/common.sh"

usage() {
    cat <<'USAGE'
Usage: vm <command> [args]

  build                       Build the golden base image (once, ~10 min)
  new <name> [--rtc <ts>] [--uefi]
                              Create a sandbox (instant; thin overlay)
  run <name> [--uefi|--bios] <command...>
                              Run a command in the sandbox, report the verdict
  start <name>                Boot the sandbox and wait until ssh answers
  wake <name> [--accel N] [--max-wait S] [--] <command...>
                              Run a command that arms the RTC and powers off or
                              hibernates; power the guest back on when its
                              CMOS alarm fires (see DOCS-rtc-wake.md)
  hibernate-setup <name> [--swap-mb N]
                              Swap + resume= so the guest can hibernate/resume
  ssh <name> [args...]        Interactive shell in the sandbox
  scp <name> <src> <dest>     Copy a host file into the sandbox
  share <host-path>           Expose a host dir to sandboxes (read-only bind)
  unshare <name>              Stop exposing it
  repo <name> <path> [--worktree]
                              Copy a host repo in (clone=HEAD, --worktree=uncommitted)
  screenshot <name> [out.png] Capture the guest screen (for locker tests)
  lightdm <name>              Switch the sandbox to lightdm autologin -> i3,
                              tty1 a plain getty (the host's login topology)
  shim <name> <tool> [mode]   Fake a hardware tool on PATH: exit[:N] | hang |
                              sleep:S; calls logged in /var/log/vmbox-shim/
  reset <name>                Wipe back to pristine (sub-second)
  rm <name>                   Delete the sandbox entirely
  list                        List sandboxes and their state

  --rtc <ts>  Pin the guest clock, e.g. 2026-08-22T20:55:00. Stored in the
              VM's meta and re-applied on EVERY launch: the shutdown scripts
              are gated on a 21:00-05:00 window, so an unpinned clock makes
              their tests nondeterministic.
  --uefi      Boot with OVMF instead of SeaBIOS. On `new` it is stored in meta
              (with a per-sandbox varstore); on `run` it is this launch only.

Examples:
  vm new demo --rtc 2026-08-22T20:55:00
  vm run demo 'sudo systemctl poweroff'      # -> verdict: clean poweroff
  vm reset demo                              # back to pristine
USAGE
}

# vm repo <name> <path> [--worktree]
cmd_repo() {
    local name="${1:-}" src="${2:-}" mode=clone
    [[ -n "$name" && -n "$src" ]] || die "usage: vm repo <name> <host-path> [--worktree]"
    shift 2
    [[ "${1:-}" == "--worktree" ]] && mode=worktree
    repo_sync "$name" "$src" "$mode"
}
# Copy a file into a sandbox. lib/ssh.sh has always been able to do this;
# without a subcommand the only route was base64 through `vm run`.
cmd_scp() {
    local name="${1:-}" src="${2:-}" dest="${3:-}"
    [[ -n "$name" && -n "$src" && -n "$dest" ]] ||
        die "usage: vm scp <name> <host-src> <guest-dest>"
    name="$(validate_vm_name "$name")"
    require_vm "$name"
    [[ -e "$src" ]] || die "no such file: $src"
    vm_is_running "$name" || die "sandbox '$name' is not running (start it with: vm run $name true)"
    vm_scp_to "$name" "$src" "$dest" || die "copy failed"
    ok "copied $src -> $name:$dest"
}

# Boot without running anything. VMBOX_START_TIMEOUT bounds the ssh wait.
cmd_start() {
    local name
    name="$(validate_vm_name "${1:-}")"
    require_vm "$name"
    vm_is_running "$name" && { log "sandbox '$name' is already running"; return 0; }
    launch_vm "$name" >/dev/null
    vm_wait_ssh "$name" "${VMBOX_START_TIMEOUT:-150}" ||
        die "sandbox '$name' did not become reachable"
    ok "sandbox '$name' is up"
}

cmd_list() {
    [[ -d "$VMBOX_VMS_DIR" ]] || { echo "no sandboxes yet (create one: vm new <name>)"; return 0; }
    local found=0 d name state rtc fw
    printf '%-20s %-12s %-6s %s\n' "NAME" "STATE" "FW" "RTC"
    for d in "$VMBOX_VMS_DIR"/*/; do
        [[ -d "$d" ]] || continue
        found=1
        name="$(basename "$d")"
        if vm_is_running "$name"; then state="running"; else state="stopped"; fi
        rtc="$(meta_get "$name" rtc 2>/dev/null || echo '-')"
        fw="$(meta_get "$name" firmware 2>/dev/null || echo bios)"
        printf '%-20s %-12s %-6s %s\n' "$name" "$state" "${fw:-bios}" "${rtc:--}"
    done
    (( found )) || echo "(none)"
}

main() {
    local cmd="${1:-}"
    [[ $# -gt 0 ]] && shift || true

    case "$cmd" in
        build)      require_host_deps; source "$VMBOX_LIB_DIR/base.sh";       base_build "$@" ;;
        new)        require_host_deps; source "$VMBOX_LIB_DIR/overlay.sh";    overlay_new "$@" ;;
        reset)      source "$VMBOX_LIB_DIR/overlay.sh";                       overlay_reset "$@" ;;
        rm)         source "$VMBOX_LIB_DIR/overlay.sh";                       overlay_rm "$@" ;;
        run)        require_host_deps; source "$VMBOX_LIB_DIR/run.sh";        run_in_vm "$@" ;;
        start)      require_host_deps; source "$VMBOX_LIB_DIR/launch.sh"
                    source "$VMBOX_LIB_DIR/ssh.sh";                           cmd_start "$@" ;;
        wake)       require_host_deps; source "$VMBOX_LIB_DIR/rtcwake.sh";    cmd_wake "$@" ;;
        hibernate-setup)
                    require_host_deps; source "$VMBOX_LIB_DIR/hibernate.sh";  cmd_hibernate_setup "$@" ;;
        ssh)        source "$VMBOX_LIB_DIR/ssh.sh";                           vm_ssh_interactive "$@" ;;
        scp)        source "$VMBOX_LIB_DIR/ssh.sh";                           cmd_scp "$@" ;;
        screenshot) source "$VMBOX_LIB_DIR/screenshot.sh";                    vm_screenshot "$@" ;;
        lightdm)    require_host_deps; source "$VMBOX_LIB_DIR/desktop.sh";    cmd_lightdm "$@" ;;
        shim)       require_host_deps; source "$VMBOX_LIB_DIR/desktop.sh";    cmd_shim "$@" ;;
        repo)       source "$VMBOX_LIB_DIR/repo.sh";                          cmd_repo "$@" ;;
        share)      source "$VMBOX_LIB_DIR/repo.sh";                          repo_share "$@" ;;
        unshare)    source "$VMBOX_LIB_DIR/repo.sh";                          repo_unshare "$@" ;;
        list|ls)    cmd_list ;;
        -h|--help|help|'') usage ;;
        *)          die "unknown command '$cmd' (try: vm --help)" ;;
    esac
}

main "$@"
