#!/bin/bash

# omarchy:summary=Install, launch, stop, inspect, or remove the Windows VM
# omarchy:args=<install|remove|launch|stop|status> [options]
# omarchy:requires-sudo=true

# The Windows VM runs a privileged container (KVM, /dev/net/tun, NET_ADMIN), so
# it needs the root-owned Docker daemon. By default the user is NOT in the docker
# group (that group is root-equivalent), so Docker access is gated behind a
# single polkit prompt. If the user opted into sudoless Docker
# (omarchy-setup-security-sudoless-docker), the socket is reachable directly and
# no prompt appears.
#
# The compose file lives in a root-owned directory and is only ever written by
# the elevated, input-validated write_compose action below. That is the whole
# point: a root-invoked `docker compose up` must never consume a file that a
# process running as the user could have rewritten to bind-mount / into the
# container. Earlier versions kept it under ~/.config/windows, which a rogue
# process could edit and then trigger a privileged bring-up of — a file-swap
# path to root. Do not move it back under $HOME.

RUNTIME_DIR="${OMARCHY_WINDOWS_DIR:-/var/lib/omarchy/windows}"
COMPOSE_FILE="$RUNTIME_DIR/docker-compose.yml"
LEGACY_COMPOSE_FILE="$HOME/.config/windows/docker-compose.yml"
# The guest password lives in the root-owned compose (readable by root and the
# docker group), but RDP needs it as the user. Keep a private copy here, 0600 in
# the user's own config, so the plaintext password is never world-readable.
CREDENTIALS_FILE="$HOME/.config/windows/credentials"
IMAGE="dockurr/windows"
CONTAINER="omarchy-windows"

# --- privilege helpers -------------------------------------------------------

# True when this session can reach the Docker socket directly (sudoless Docker
# on and in effect). Asking about the socket rather than the configured groups
# keeps the prompt in place through the window where sudoless Docker is enabled
# but the reboot that grants the group has not happened yet.
docker_needs_sudo() { omarchy-sudo-docker; }

# The command to hand pkexec for the privileged re-exec. pkexec runs whatever
# executable it is given (after authorization) and only shows the path in the
# prompt — it does NOT require the target to be root-owned. So resolve to the
# packaged command and refuse to elevate anything a non-root user could have
# written: a PATH-injected shim, or a user-owned dev checkout. Without this, a
# prompt the user grants for the trusted helper could run an attacker's binary
# as root. Fails closed (empty output) when no trustworthy target is found.
priv_target() {
  local candidate owner mode
  for candidate in /usr/bin/omarchy-windows-vm "$(type -P omarchy-windows-vm 2>/dev/null)"; do
    [[ -n $candidate && -x $candidate ]] || continue
    owner=$(stat -Lc '%u' "$candidate" 2>/dev/null) || continue
    mode=$(stat -Lc '%a' "$candidate" 2>/dev/null) || continue
    [[ $owner == "0" ]] || continue
    ((8#$mode & 022)) && continue # writable by group or other -> reject
    printf '%s\n' "$candidate"
    return 0
  done
  return 1
}

# Run a privileged VM action. write_compose always elevates (the compose is
# root-owned); the daemon operations run directly when sudoless Docker is on and
# otherwise behind a polkit prompt. The stock org.freedesktop.policykit.exec
# policy is auth_admin (not auth_admin_keep), so each elevated action prompts:
# a launch asks once to start and, unless authorization is still cached by the
# agent, again to stop.
priv() {
  local action="$1"
  shift
  if [[ $action != write_compose ]] && ! docker_needs_sudo; then
    "__priv_$action" "$@"
    return
  fi
  local target
  target=$(priv_target) || {
    echo "omarchy-windows-vm: refusing to run a non-root-owned command as root" >&2
    return 1
  }
  pkexec "$target" __priv "$action" "$@"
}

dc() { docker-compose -f "$COMPOSE_FILE" "$@"; }

# --- validation (shared by the user-side prompts and the root-side writer) ----

valid_ram() { [[ $1 =~ ^[0-9]{1,3}G$ ]]; }
valid_cores() { [[ $1 =~ ^[0-9]{1,2}$ ]] && ((10#$1 >= 1)); }
valid_disk() { [[ $1 =~ ^[0-9]{1,4}G$ ]]; }
valid_username() { [[ $1 =~ ^[A-Za-z0-9_-]{1,20}$ ]]; }
valid_tz() { [[ $1 =~ ^[A-Za-z0-9_/.+-]{1,64}$ ]]; }
valid_path() {
  [[ $1 =~ ^/[A-Za-z0-9._/-]+$ ]] || return 1
  # Reject non-normalized paths: a . or .. component canonicalizes at mount time
  # (e.g. /./ or /a/../ -> /), which would bind-mount a sensitive directory —
  # host / included — into the guest. Volumes must be given already-normalized.
  case "$1" in
  *//* | */./* | */../* | */. | */..) return 1 ;;
  esac
  return 0
}
valid_password() { [[ $1 =~ ^[[:print:]]{1,64}$ ]]; }

# The only privileged sub-actions __priv may dispatch. A bash command name
# containing a slash is executed as a path, so validating the action here — not
# just interpolating it into "__priv_${action}" — is what stops an action like
# ../tmp/evil from running an arbitrary file as root.
valid_priv_action() {
  case "$1" in
  write_compose | up | up_wait | down | status | remove) return 0 ;;
  *) return 1 ;;
  esac
}

# --- privileged actions (run as root via pkexec, or directly when sudoless) ---

# Reads KEY=VALUE lines on stdin, re-validates every field, and writes the
# compose atomically as root. Re-validation here is the security boundary: the
# writer refuses rather than emit a compose an attacker could have influenced.
# Only these fixed keys are honored; image, container name, devices, caps, and
# port bindings are hard-coded and never taken from input.
__priv_write_compose() {
  local ram cores disk username password tz storage shared key value

  while IFS='=' read -r key value; do
    case "$key" in
    RAM) ram="$value" ;;
    CORES) cores="$value" ;;
    DISK) disk="$value" ;;
    USERNAME) username="$value" ;;
    PASSWORD) password="$value" ;;
    TZ) tz="$value" ;;
    STORAGE) storage="$value" ;;
    SHARED) shared="$value" ;;
    esac
  done

  valid_ram "$ram" || { echo "invalid RAM: $ram" >&2; exit 2; }
  valid_cores "$cores" || { echo "invalid CPU cores: $cores" >&2; exit 2; }
  valid_disk "$disk" || { echo "invalid disk size: $disk" >&2; exit 2; }
  valid_username "$username" || { echo "invalid username: $username" >&2; exit 2; }
  valid_password "$password" || { echo "invalid password" >&2; exit 2; }
  valid_tz "$tz" || tz="UTC"
  valid_path "$storage" || { echo "invalid storage path: $storage" >&2; exit 2; }
  valid_path "$shared" || { echo "invalid shared path: $shared" >&2; exit 2; }

  # Neutralize anything in the password that could be misread when the compose
  # is parsed. Two layers apply, in this order at parse time: docker compose
  # variable interpolation over the raw text ($VAR / $$), then YAML parsing of
  # the double-quoted scalar. Encode for the inner layer first (backslash, then
  # double-quote) and the interpolation layer last ($ -> $$), so a password
  # containing " \ or $ reaches the guest verbatim. unescape() reverses this in
  # the opposite order for the RDP credentials.
  local esc_password=${password//\\/\\\\}
  esc_password=${esc_password//\"/\\\"}
  esc_password=${esc_password//\$/\$\$}

  mkdir -p "$RUNTIME_DIR"
  chmod 0755 "$RUNTIME_DIR" 2>/dev/null || true
  chown root:root "$RUNTIME_DIR" 2>/dev/null || true

  local tmp
  tmp=$(mktemp "$RUNTIME_DIR/.compose.XXXXXX")
  cat >"$tmp" <<EOF
services:
  windows:
    image: $IMAGE
    container_name: $CONTAINER
    environment:
      VERSION: "11"
      RAM_SIZE: "$ram"
      CPU_CORES: "$cores"
      DISK_SIZE: "$disk"
      USERNAME: "$username"
      PASSWORD: "$esc_password"
      TZ: "$tz"
      ARGUMENTS: "-rtc base=localtime,clock=host,driftfix=slew"
    devices:
      - /dev/kvm
      - /dev/net/tun
    cap_add:
      - NET_ADMIN
    ports:
      - 127.0.0.1:8006:8006
      - 127.0.0.1:3389:3389/tcp
      - 127.0.0.1:3389:3389/udp
    volumes:
      - $storage:/storage
      - $shared:/shared
    restart: "no"
    stop_grace_period: 2m
EOF
  # Readable by root and the docker group only. In sudoless mode the user is in
  # the docker group and runs docker-compose against this file directly; in the
  # default mode the elevated helper (root) reads it, and other local users
  # cannot read the guest password. chown falls back to root:root if the docker
  # group somehow does not exist (sudoless mode could not be enabled anyway).
  chmod 0640 "$tmp" 2>/dev/null || true
  chown root:docker "$tmp" 2>/dev/null || chown root:root "$tmp" 2>/dev/null || true
  mv -f "$tmp" "$COMPOSE_FILE"
}

# Read the host source of a bind mount out of the compose (e.g. /storage).
# valid_path kept a ':' out of the stored path, so splitting on it is safe.
get_mount_source() {
  sed -n "s|^[[:space:]]*-[[:space:]]*\(/[^:]*\):$1\$|\1|p" "$COMPOSE_FILE" | head -n1
}

# Refuse to bring the VM up if a bind-mount source is a symlink, or reached
# through one. valid_path keeps a traversal string like /./ out of the compose,
# but a symlink planted at ~/.windows or ~/Windows would redirect the privileged
# mount just the same — docker follows it — and a string check cannot see that.
# So verify the real directories here, as root, immediately before the mount. A
# source that does not exist is fine: docker creates it as a plain directory.
assert_mounts_safe() {
  local mnt src real
  for mnt in /storage /shared; do
    src=$(get_mount_source "$mnt")
    [[ -n $src ]] || {
      echo "omarchy-windows-vm: missing $mnt mount source in the compose" >&2
      return 1
    }
    if [[ -L $src ]]; then
      echo "omarchy-windows-vm: refusing to start — $src is a symlink; the VM mount source must be a real directory" >&2
      return 1
    fi
    if [[ -e $src ]]; then
      [[ -d $src ]] || {
        echo "omarchy-windows-vm: refusing to start — $src is not a directory" >&2
        return 1
      }
      real=$(realpath "$src" 2>/dev/null)
      [[ $real == "$src" ]] || {
        echo "omarchy-windows-vm: refusing to start — $src resolves through a symlink to $real" >&2
        return 1
      }
    fi
  done
}

__priv_up() { assert_mounts_safe && dc up -d; }

__priv_down() { dc down; }

# Bring the VM up and wait until the guest reports it is ready, all under a
# single elevation so the readiness poll does not prompt on every iteration.
__priv_up_wait() {
  assert_mounts_safe || return 1
  local status
  status=$(docker inspect --format='{{.State.Status}}' "$CONTAINER" 2>/dev/null)
  if [[ $status != "running" ]]; then
    dc up -d || return 1
  fi

  # docker logs persists across restarts, so anchor the scan to the current
  # start time; an empty --since would match a stale "started successfully".
  local started_at count=0
  while true; do
    started_at=$(docker inspect --format='{{.State.StartedAt}}' "$CONTAINER" 2>/dev/null)
    if [[ -n $started_at ]] && docker logs --since "$started_at" "$CONTAINER" 2>&1 | grep -qi "windows started successfully"; then
      return 0
    fi
    sleep 2
    ((++count > 60)) && {
      echo "Timeout: Windows VM did not report ready within 2 minutes" >&2
      return 1
    }
  done
}

# Print the status (empty if the container does not exist) and always succeed,
# so a non-zero exit from priv status means the elevation itself failed
# (authorization declined) rather than "no such container".
__priv_status() { docker inspect --format='{{.State.Status}}' "$CONTAINER" 2>/dev/null || true; }

__priv_remove() {
  dc down 2>/dev/null || true
  docker rmi "$IMAGE" 2>/dev/null || true
  rm -f "$COMPOSE_FILE"
  rmdir "$RUNTIME_DIR" 2>/dev/null || true
}

# --- config helpers ----------------------------------------------------------

# Feed the collected settings to the elevated writer.
write_compose() {
  local ram="$1" cores="$2" disk="$3" username="$4" password="$5" tz="$6" storage="$7" shared="$8"
  printf 'RAM=%s\nCORES=%s\nDISK=%s\nUSERNAME=%s\nPASSWORD=%s\nTZ=%s\nSTORAGE=%s\nSHARED=%s\n' \
    "$ram" "$cores" "$disk" "$username" "$password" "$tz" "$storage" "$shared" |
    priv write_compose
}

# Reverse, in the opposite order, the escaping __priv_write_compose applied to
# the password: undo the interpolation layer ($$ -> $) first, then the YAML
# layer (\" -> ", then \\ -> \).
unescape() {
  local v=$1
  v=${v//\$\$/\$}
  v=${v//\\\"/\"}
  v=${v//\\\\/\\}
  printf '%s' "$v"
}

# Store the RDP credentials privately for the user (0600) so the plaintext
# password is not world-readable. The password is one validated printable line
# (no newline), so plain KEY=VALUE is safe.
write_credentials() {
  local username="$1" password="$2" old_umask
  mkdir -p "$(dirname "$CREDENTIALS_FILE")"
  old_umask=$(umask)
  umask 077
  printf 'USERNAME=%s\nPASSWORD=%s\n' "$username" "$password" >"$CREDENTIALS_FILE"
  chmod 600 "$CREDENTIALS_FILE" 2>/dev/null || true
  umask "$old_umask"
}

# Read one field from the private credentials file; IFS on the first = keeps a
# password that itself contains =.
read_credential() {
  local want="$1" key value
  [[ -f $CREDENTIALS_FILE ]] || return 1
  while IFS='=' read -r key value; do
    [[ $key == "$want" ]] && {
      printf '%s' "$value"
      return 0
    }
  done <"$CREDENTIALS_FILE"
  return 1
}

read_compose_value() {
  local key="$1" file="$2"
  sed -n "s/.*${key}: \"\(.*\)\"/\1/p" "$file" | head -n1
}

# Older installs kept the compose under ~/.config/windows. Carry those settings
# into the root-owned location (preserving the VM's data via the same volume
# paths) so an upgrade does not strand or re-download an existing VM.
migrate_legacy_compose() {
  [[ -f $COMPOSE_FILE ]] && return 0
  [[ -f $LEGACY_COMPOSE_FILE ]] || return 1

  echo "Migrating Windows VM configuration to $COMPOSE_FILE ..."
  local ram cores disk username password tz storage shared
  ram=$(read_compose_value RAM_SIZE "$LEGACY_COMPOSE_FILE")
  cores=$(read_compose_value CPU_CORES "$LEGACY_COMPOSE_FILE")
  disk=$(read_compose_value DISK_SIZE "$LEGACY_COMPOSE_FILE")
  username=$(read_compose_value USERNAME "$LEGACY_COMPOSE_FILE")
  password=$(read_compose_value PASSWORD "$LEGACY_COMPOSE_FILE")
  tz=$(read_compose_value TZ "$LEGACY_COMPOSE_FILE")
  # The VM's data always lived in the user's own ~/.windows and ~/Windows; the
  # old compose only ever recorded those. Reconstruct them from $HOME (trusted —
  # this runs as the user) rather than reading host paths back from a file a
  # rogue process could have rewritten to bind-mount, say, / into the guest.
  storage="$HOME/.windows"
  shared="$HOME/Windows"

  [[ -z $tz ]] && tz="UTC"
  if ! write_compose "$ram" "$cores" "$disk" "$username" "$password" "$tz" "$storage" "$shared"; then
    echo "Could not migrate the existing configuration automatically." >&2
    echo "Re-run: omarchy-windows-vm install" >&2
    return 1
  fi
  write_credentials "$username" "$password"
  rm -f "$LEGACY_COMPOSE_FILE"
}

# --- prerequisites -----------------------------------------------------------

check_prerequisites() {
  local DISK_SIZE_GB=${1:-64}
  local REQUIRED_SPACE=$((DISK_SIZE_GB + 10)) # Add 10GB for Windows ISO and overhead

  # Check for KVM support
  if [[ ! -e /dev/kvm ]]; then
    gum style \
      --border normal \
      --padding "1 2" \
      --margin "1" \
      "❌ KVM virtualization not available!" \
      "" \
      "Please enable virtualization in BIOS or run:" \
      "  sudo modprobe kvm-intel  # for Intel CPUs" \
      "  sudo modprobe kvm-amd    # for AMD CPUs"
    exit 1
  fi

  # Check disk space
  AVAILABLE_SPACE=$(df "$HOME" | awk 'NR==2 {print int($4/1024/1024)}')
  if ((AVAILABLE_SPACE < REQUIRED_SPACE)); then
    echo "❌ Insufficient disk space!"
    echo "   Available: ${AVAILABLE_SPACE}GB"
    echo "   Required: ${REQUIRED_SPACE}GB (${DISK_SIZE_GB}GB disk + 10GB for Windows image)"
    exit 1
  fi
}

# --- commands ----------------------------------------------------------------

install_windows() {
  # Set up trap to handle Ctrl+C
  trap "echo ''; echo 'Installation cancelled by user'; exit 1" INT

  check_prerequisites

  omarchy-pkg-add freerdp openbsd-netcat gum

  mkdir -p "$HOME/.windows"
  mkdir -p "$HOME/.local/share/applications"

  cat <<EOF | tee "$HOME/.local/share/applications/windows-vm.desktop" >/dev/null
[Desktop Entry]
Name=Windows
Comment=Start Windows VM via Docker and connect with RDP
Exec=uwsm app -- omarchy-windows-vm launch
Icon=windows
Terminal=false
Type=Application
Categories=System;Virtualization;
EOF

  # Get system resources
  TOTAL_RAM=$(free -h | awk 'NR==2 {print $2}')
  TOTAL_RAM_GB=$(awk 'NR==1 {printf "%d", $2/1024/1024}' /proc/meminfo)
  TOTAL_CORES=$(nproc)

  echo ""
  echo "System Resources Detected:"
  echo "  Total RAM: $TOTAL_RAM"
  echo "  Total CPU Cores: $TOTAL_CORES"
  echo ""

  RAM_OPTIONS=""
  for size in 2 4 8 16 32 64; do
    if ((size <= TOTAL_RAM_GB)); then
      RAM_OPTIONS="$RAM_OPTIONS ${size}G"
    fi
  done

  SELECTED_RAM=$(echo $RAM_OPTIONS | tr ' ' '\n' | gum choose --selected="4G" --header="How much RAM would you like to allocate to Windows VM?")

  # Check if user cancelled
  if [[ -z $SELECTED_RAM ]]; then
    echo "Installation cancelled by user"
    exit 1
  fi

  SELECTED_CORES=$(gum input --placeholder="Number of CPU cores (1-$TOTAL_CORES)" --value="2" --header="How many CPU cores would you like to allocate to Windows VM?" --char-limit=2)

  # Check if user cancelled (Ctrl+C in gum input returns empty string)
  if [[ -z $SELECTED_CORES ]]; then
    echo "Installation cancelled by user"
    exit 1
  fi

  if ! valid_cores "$SELECTED_CORES" || ((SELECTED_CORES > TOTAL_CORES)); then
    echo "Invalid input. Using default: 2 cores"
    SELECTED_CORES=2
  fi

  AVAILABLE_SPACE=$(df "$HOME" | awk 'NR==2 {print int($4/1024/1024)}')
  MAX_DISK_GB=$((AVAILABLE_SPACE - 10)) # Leave 10GB for Windows image

  # Check if we have enough space for minimum
  if ((MAX_DISK_GB < 32)); then
    echo "❌ Insufficient disk space for Windows VM!"
    echo "   Available: ${AVAILABLE_SPACE}GB"
    echo "   Minimum required: 42GB (32GB disk + 10GB for Windows image)"
    exit 1
  fi

  DISK_OPTIONS=""
  for size in 32 64 128 256 512; do
    if ((size <= MAX_DISK_GB)); then
      DISK_OPTIONS="$DISK_OPTIONS ${size}G"
    fi
  done

  # Default to 64G if available, otherwise 32G
  DEFAULT_DISK="64G"
  if ! echo "$DISK_OPTIONS" | grep -q "64G"; then
    DEFAULT_DISK="32G"
  fi

  SELECTED_DISK=$(echo $DISK_OPTIONS | tr ' ' '\n' | gum choose --selected="$DEFAULT_DISK" --header="How much disk space would you like to give Windows VM? (64GB+ recommended)")

  # Check if user cancelled
  if [[ -z $SELECTED_DISK ]]; then
    echo "Installation cancelled by user"
    exit 1
  fi

  # Extract just the number for prerequisite check
  DISK_SIZE_NUM=$(echo "$SELECTED_DISK" | sed 's/G//')

  # Re-check prerequisites with selected disk size
  check_prerequisites "$DISK_SIZE_NUM"

  # Prompt for username and password
  USERNAME=$(gum input --placeholder="Username (Press enter to use default: docker)" --header="Enter Windows username:")
  if [[ -z $USERNAME ]]; then
    USERNAME="docker"
  fi
  if ! valid_username "$USERNAME"; then
    echo "Invalid username (use letters, digits, - or _, up to 20 chars). Using default: docker"
    USERNAME="docker"
  fi

  PASSWORD=$(gum input --placeholder="Password (Press enter to use default: admin)" --password --header="Enter Windows password:")
  if [[ -z $PASSWORD ]]; then
    PASSWORD="admin"
    PASSWORD_DISPLAY="(default)"
  else
    PASSWORD_DISPLAY="(user-defined)"
  fi
  if ! valid_password "$PASSWORD"; then
    echo "Invalid password (printable characters, up to 64). Using default: admin"
    PASSWORD="admin"
    PASSWORD_DISPLAY="(default)"
  fi

  # Display configuration summary
  gum style \
    --border normal \
    --padding "1 2" \
    --margin "1" \
    --align left \
    --bold \
    "Windows VM Configuration" \
    "" \
    "RAM:       $SELECTED_RAM" \
    "CPU:       $SELECTED_CORES cores" \
    "Disk:      $SELECTED_DISK" \
    "Username:  $USERNAME" \
    "Password:  $PASSWORD_DISPLAY"

  # Ask for confirmation
  echo ""
  if ! gum confirm "Proceed with this configuration?"; then
    echo "Installation cancelled by user"
    exit 1
  fi

  mkdir -p "$HOME/Windows"

  local tz
  tz=$(timedatectl show -p Timezone --value 2>/dev/null || echo UTC)

  # Write the root-owned compose from the validated settings (one prompt if
  # sudoless Docker is off), then bring the stack up.
  write_compose "$SELECTED_RAM" "$SELECTED_CORES" "$SELECTED_DISK" \
    "$USERNAME" "$PASSWORD" "$tz" "$HOME/.windows" "$HOME/Windows" || {
    echo "❌ Failed to write the Windows VM configuration."
    exit 1
  }
  write_credentials "$USERNAME" "$PASSWORD"

  echo ""
  echo "Starting Windows VM installation..."
  echo "This will download a Windows 11 image (may take 10-15 minutes)."
  echo ""
  echo "Monitor installation progress at: http://127.0.0.1:8006"
  echo ""

  echo "Starting Windows VM with docker-compose..."
  if ! priv up; then
    echo "❌ Failed to start Windows VM!"
    echo "   Common issues:"
    echo "   - Docker daemon not running: sudo systemctl start docker"
    echo "   - Port already in use: check if another VM is running"
    exit 1
  fi

  echo ""
  echo "Windows VM is starting up!"
  echo ""
  echo "Opening browser to monitor installation..."

  # Open browser to monitor installation
  sleep 3
  xdg-open "http://127.0.0.1:8006"

  echo ""
  echo "Installation is running in the background."
  echo "You can monitor progress at: http://127.0.0.1:8006"
  echo ""
  echo "Once finished, launch 'Windows' via Super + Space"
  echo ""
  echo "To stop the VM: omarchy-windows-vm stop"
  echo ""
}

remove_windows() {
  if ! gum confirm --default=false "Remove Windows VM and delete all associated data?"; then
    echo "Removal cancelled by user"
    exit 1
  fi

  echo "Removing Windows VM..."

  migrate_legacy_compose 2>/dev/null || true
  if [[ -f $COMPOSE_FILE ]]; then
    priv remove || true
  fi

  rm -f "$HOME/.local/share/applications/windows-vm.desktop"
  rm -rf "$HOME/.config/windows"
  rm -rf "$HOME/.windows"

  echo ""
  echo "Windows VM removal completed!"
}

launch_windows() {
  KEEP_ALIVE=false
  if [[ $1 = "--keep-alive" ]] || [[ $1 = "-k" ]]; then
    KEEP_ALIVE=true
  fi

  if ! migrate_legacy_compose; then
    if [[ ! -f $COMPOSE_FILE ]]; then
      echo "Windows VM not configured. Please run: omarchy-windows-vm install"
      exit 1
    fi
  fi

  # RDP credentials come from the private per-user file. Fall back to the compose
  # only when it is readable (sudoless mode), reversing the writer's escaping.
  WIN_USER=$(read_credential USERNAME) || WIN_USER=""
  WIN_PASS=$(read_credential PASSWORD) || WIN_PASS=""
  if [[ -z $WIN_USER || -z $WIN_PASS ]] && [[ -r $COMPOSE_FILE ]]; then
    [[ -z $WIN_USER ]] && WIN_USER=$(unescape "$(read_compose_value USERNAME "$COMPOSE_FILE")")
    [[ -z $WIN_PASS ]] && WIN_PASS=$(unescape "$(read_compose_value PASSWORD "$COMPOSE_FILE")")
  fi
  [[ -z $WIN_USER ]] && WIN_USER="docker"
  [[ -z $WIN_PASS ]] && WIN_PASS="admin"

  echo "Starting Windows VM (this may prompt for authorization)..."
  if ! priv up_wait; then
    echo "❌ Failed to start Windows VM!"
    echo "   Try checking: omarchy-windows-vm status"
    omarchy-notification-send -u critical "Windows VM" "Failed to start Windows VM"
    exit 1
  fi

  # Build the connection info
  if [[ $KEEP_ALIVE = "true" ]]; then
    LIFECYCLE="VM will keep running after RDP closes
To stop: omarchy-windows-vm stop"
  else
    LIFECYCLE="VM will auto-stop when RDP closes"
  fi

  gum style \
    --border normal \
    --padding "1 2" \
    --margin "1" \
    --align center \
    "Connecting to Windows VM" \
    "" \
    "$LIFECYCLE"

  # FreeRDP 3 tries Kerberos before NTLM for NLA, and krb5 ships /etc/krb5.conf
  # as the MIT sample with default_realm = ATHENA.MIT.EDU. Every connect then
  # goes looking for MIT's KDC: with internet up it fails fast, but off the
  # network each attempt blocks ~23s and no RDP window is ever drawn. The VM
  # authenticates against a local Windows account, so point FreeRDP at a
  # realm-less config and let it fall straight through to NTLM.
  KRB5_CONF="$HOME/.config/windows/krb5.conf"
  mkdir -p "$(dirname "$KRB5_CONF")"
  if [[ ! -f $KRB5_CONF ]]; then
    printf '[libdefaults]\n  dns_lookup_kdc = false\n  dns_lookup_realm = false\n' >"$KRB5_CONF"
  fi
  export KRB5_CONFIG="$KRB5_CONF"

  # Detect display scale from Hyprland
  HYPR_SCALE=$(hyprctl monitors -j | jq -r '.[] | select (.focused == true) | .scale')
  SCALE_PERCENT=$(echo "$HYPR_SCALE" | awk '{print int($1 * 100)}')

  RDP_SCALE=""
  if ((SCALE_PERCENT >= 170)); then
    RDP_SCALE="/scale:180"
  elif ((SCALE_PERCENT >= 130)); then
    RDP_SCALE="/scale:140"
  fi
  # If scale is less than 130%, don't set any scale (use default 100)

  # Connect with RDP in fullscreen (auto-detects resolution)
  xfreerdp3 /u:"$WIN_USER" /p:"$WIN_PASS" /v:127.0.0.1:3389 -grab-keyboard /sound /microphone /clipboard /cert:ignore /title:"Windows VM - Omarchy" /dynamic-resolution /gfx:AVC444 /floatbar:sticky:off,default:visible,show:fullscreen $RDP_SCALE

  # After RDP closes, stop the container unless --keep-alive was specified
  if [[ $KEEP_ALIVE = "false" ]]; then
    echo ""
    echo "RDP session closed. Stopping Windows VM..."
    if priv down; then
      echo "Windows VM stopped."
    else
      echo "⚠️  Could not stop the Windows VM (authorization declined?)."
      echo "   It may still be running. Stop it with: omarchy-windows-vm stop"
    fi
  else
    echo ""
    echo "RDP session closed. Windows VM is still running."
    echo "To stop it: omarchy-windows-vm stop"
  fi
}

stop_windows() {
  migrate_legacy_compose 2>/dev/null || true
  if [[ ! -f $COMPOSE_FILE ]]; then
    echo "Windows VM not configured."
    exit 1
  fi

  echo "Stopping Windows VM..."
  if priv down; then
    echo "Windows VM stopped."
  else
    echo "⚠️  Could not stop the Windows VM (authorization declined?). It may still be running."
    exit 1
  fi
}

status_windows() {
  migrate_legacy_compose 2>/dev/null || true
  if [[ ! -f $COMPOSE_FILE ]]; then
    echo "Windows VM not configured."
    echo "To set up: omarchy-windows-vm install"
    exit 1
  fi

  if ! CONTAINER_STATUS=$(priv status); then
    echo "Could not query the Windows VM (authorization declined?)."
    echo "To try again: omarchy-windows-vm status"
    exit 1
  fi

  if [[ -z $CONTAINER_STATUS ]]; then
    echo "Windows VM container not found."
    echo "To start: omarchy-windows-vm launch"
  elif [[ $CONTAINER_STATUS = "running" ]]; then
    gum style \
      --border normal \
      --padding "1 2" \
      --margin "1" \
      --align left \
      "Windows VM Status: RUNNING" \
      "" \
      "Web interface: http://127.0.0.1:8006" \
      "RDP available: port 3389" \
      "" \
      "To connect: omarchy-windows-vm launch" \
      "To stop:    omarchy-windows-vm stop"
  else
    echo "Windows VM is stopped (status: $CONTAINER_STATUS)"
    echo "To start: omarchy-windows-vm launch"
  fi
}

show_usage() {
  echo "Usage: omarchy-windows-vm [command] [options]"
  echo ""
  echo "Commands:"
  echo "  install              Install and configure Windows VM"
  echo "  remove               Remove Windows VM and optionally its data"
  echo "  launch [options]     Start Windows VM (if needed) and connect via RDP"
  echo "                       Options:"
  echo "                         --keep-alive, -k   Keep VM running after RDP closes"
  echo "  stop                 Stop the running Windows VM"
  echo "  status               Show current VM status"
  echo "  help                 Show this help message"
  echo ""
  echo "Examples:"
  echo "  omarchy-windows-vm install           # Set up Windows VM for first time"
  echo "  omarchy-windows-vm launch            # Connect to VM (auto-stop on exit)"
  echo "  omarchy-windows-vm launch -k         # Connect to VM (keep running)"
  echo "  omarchy-windows-vm stop              # Shut down the VM"
}

# Main command dispatcher
case "$1" in
__priv)
  ((EUID == 0)) || {
    echo "omarchy-windows-vm __priv must run as root" >&2
    exit 1
  }
  action="$2"
  shift 2
  valid_priv_action "$action" || {
    echo "omarchy-windows-vm: unknown privileged action" >&2
    exit 1
  }
  "__priv_${action}" "$@"
  ;;
install)
  install_windows
  ;;
remove)
  remove_windows
  ;;
launch | start)
  launch_windows "$2"
  ;;
stop | down)
  stop_windows
  ;;
status)
  status_windows
  ;;
help | --help | -h | "")
  show_usage
  ;;
*)
  echo "Unknown command: $1" >&2
  echo "" >&2
  show_usage >&2
  exit 1
  ;;
esac
