#!/usr/bin/env bash
# DevGrail uninstaller — companion to install.sh.
#
#   curl -fsSL https://<devgrail-web>/uninstall.sh | sudo bash              # preserve data
#   curl -fsSL https://<devgrail-web>/uninstall.sh | sudo bash -s -- --purge # full wipe
#   sudo bash uninstall.sh            # tear down, PRESERVE data volumes + config
#   sudo bash uninstall.sh --purge    # full wipe: also remove data, certs, files
#
# --purge takes a backup (database + config.yaml) before deleting anything, so
# the one irreversible command in the product still leaves a way back. Pass
# --no-backup to skip it — which is the only way to destroy the data outright.
#
# Removes what install.sh created: the Compose stack (infra containers +
# managed networks + volumes), the separately-spawned workspace containers and
# volumes (labeled `devgrail.managed`, not owned by Compose), the external
# `devgrail` edge network, the loaded images, and — with --purge — the persistent
# data volumes and the on-disk config in /opt/devgrail and /etc/devgrail.
#
# Default run preserves the SQLite data volume (devgrail-data), the Let's Encrypt
# certs (traefik-acme), all workspace volumes, and the config files, so a later
# re-run of install.sh resumes in place. --purge leaves nothing behind.
#
# Docker itself and jq (which install.sh may have installed) are left alone.
#
# Vars:
#   DEVGRAIL_INSTALL_DIR  install root (default /opt/devgrail)
set -euo pipefail

INSTALL_DIR="${DEVGRAIL_INSTALL_DIR:-/opt/devgrail}"
DEPLOY_DIR="$INSTALL_DIR/deploy"
CONFIG_DIR=/etc/devgrail
COMPOSE_FILE="$DEPLOY_DIR/docker-compose.yml"

PURGE=0
ASSUME_YES=0
# A --purge takes a backup first unless the operator explicitly opts out. It is
# the only irreversible operation here: the database, every workspace volume and
# the config.yaml whose jwt_secret decrypts the database all go at once.
NO_BACKUP=0

log()  { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33mWARNING:\033[0m %s\n' "$*" >&2; }
die()  { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }

usage() {
  cat <<EOF
Usage: sudo bash uninstall.sh [--purge] [--no-backup] [--yes]

  --purge   Also remove persistent data (devgrail-data volume, traefik-acme
            certs, workspace volumes) and the on-disk config in $INSTALL_DIR
            and $CONFIG_DIR. Without this, those are preserved so install.sh
            can resume in place.
  --yes     Do not prompt for confirmation.
  --no-backup
            With --purge, skip the automatic pre-wipe backup. Without this,
            --purge writes a database + config.yaml archive to
            $INSTALL_DIR/backups first and refuses to continue if it cannot.
  -h, --help  Show this help.
EOF
}

while [ $# -gt 0 ]; do
  case "$1" in
    --purge)     PURGE=1 ;;
    --no-backup) NO_BACKUP=1 ;;
    --yes|-y)    ASSUME_YES=1 ;;
    -h|--help)   usage; exit 0 ;;
    *) die "unknown argument: $1 (see --help)" ;;
  esac
  shift
done

[ "$(id -u)" = 0 ] || die "run as root: sudo bash uninstall.sh"
command -v docker >/dev/null 2>&1 || die "docker not found — nothing to do, or already removed"

# --- confirm + decide whether to wipe data ---------------------------------
# stdin is the piped installer, so questions go to /dev/tty. Returns 0 on yes.
ask() {
  local msg="$1" ans
  printf '%s ' "$msg" > /dev/tty
  read -r ans < /dev/tty || ans=""
  case "$ans" in y|Y|yes|YES) return 0 ;; *) return 1 ;; esac
}

if [ "$ASSUME_YES" = 1 ]; then
  : # honour --purge/flags as given, no prompts
elif [ -e /dev/tty ]; then
  if [ "$PURGE" = 1 ]; then
    warn "PURGE mode: this permanently deletes the DevGrail database, TLS certs,"
    warn "all workspace volumes, and config in $INSTALL_DIR and $CONFIG_DIR."
    ask "Permanently delete everything? [y/N]" || die "aborted"
  else
    # Preserve is the default; offer the wipe rather than requiring the flag.
    if ask "Also delete ALL DevGrail data — workspaces, database, certs, config? [y/N]"; then
      PURGE=1
    fi
  fi
else
  # No terminal: never prompt. Preserve is safe to run unattended; a wipe is not,
  # so --purge without a terminal must be confirmed with --yes.
  [ "$PURGE" = 1 ] && die "refusing to --purge without a terminal — pass --yes to confirm"
fi

if [ "$PURGE" = 1 ]; then
  log "Uninstalling DevGrail — FULL WIPE (data, certs, and config will be removed)."
else
  log "Uninstalling DevGrail (data volumes + config preserved; use --purge to wipe)."
fi

# --- 0. pre-wipe backup (purge only) ---------------------------------------
# Taken before anything is torn down, while the server is still up and the
# maintenance CLI can still resolve the data volume. A failure here aborts:
# proceeding would destroy the data this backup exists to preserve, and an
# operator who genuinely wants that has --no-backup to say so.
if [ "$PURGE" = 1 ] && [ "$NO_BACKUP" = 0 ]; then
  if [ -x /usr/local/bin/devgrail ]; then
    log "Taking a final backup before wiping..."
    /usr/local/bin/devgrail backup || die "the pre-wipe backup failed — refusing to destroy data that has not been backed up.
  Fix the cause and re-run, or pass --no-backup to wipe anyway."
    log "Backup kept in $INSTALL_DIR/backups — it survives this uninstall only if you copy it off this host first."
  else
    warn "no /usr/local/bin/devgrail on this host, so no pre-wipe backup can be taken."
    warn "This install predates the maintenance CLI. Re-run install.sh to get it, or"
    warn "pass --no-backup to confirm you are destroying the data unrecoverably."
    [ "$ASSUME_YES" = 1 ] || die "aborted (pass --no-backup to wipe without a backup)"
  fi
fi

# --- 1. tear down the Compose stack ---------------------------------------
# Infra containers (traefik, devgrail-server, docker-socket-proxy), the managed
# devgrail-control/devgrail-docker networks, and — with --purge — the named
# volumes (devgrail-data, traefik-acme).
if [ -f "$COMPOSE_FILE" ] && docker compose version >/dev/null 2>&1; then
  log "Stopping the DevGrail Compose stack..."
  if [ "$PURGE" = 1 ]; then
    docker compose --project-directory "$DEPLOY_DIR" -f "$COMPOSE_FILE" down -v --remove-orphans || \
      warn "compose down reported an error — continuing"
  else
    docker compose --project-directory "$DEPLOY_DIR" -f "$COMPOSE_FILE" down --remove-orphans || \
      warn "compose down reported an error — continuing"
  fi
else
  warn "no compose file at $COMPOSE_FILE — removing infra containers directly"
  docker rm -f devgrail-server devgrail-docker-proxy 2>/dev/null || true
fi

# --- 2. workspace containers + volumes (labeled, not Compose-owned) --------
log "Removing workspace containers..."
ws_containers="$(docker ps -aq --filter label=devgrail.managed 2>/dev/null || true)"
[ -n "$ws_containers" ] && docker rm -f $ws_containers >/dev/null 2>&1 || true

if [ "$PURGE" = 1 ]; then
  log "Removing workspace volumes..."
  ws_volumes="$(docker volume ls -q --filter label=devgrail.managed 2>/dev/null || true)"
  [ -n "$ws_volumes" ] && docker volume rm $ws_volumes >/dev/null 2>&1 || true
else
  ws_volumes="$(docker volume ls -q --filter label=devgrail.managed 2>/dev/null || true)"
  [ -n "$ws_volumes" ] && log "Preserved $(printf '%s\n' "$ws_volumes" | grep -c .) workspace volume(s) (use --purge to remove)."
fi

# --- 3. external edge network ----------------------------------------------
# Created out-of-band by install.sh (external: true in the compose), so `down`
# never removes it. Only removable once all attached containers are gone.
if docker network inspect devgrail >/dev/null 2>&1; then
  log "Removing the devgrail edge network..."
  docker network rm devgrail >/dev/null 2>&1 || \
    warn "could not remove the 'devgrail' network (still in use?) — remove manually once containers are gone"
fi

# --- 4. images -------------------------------------------------------------
log "Removing DevGrail images..."
docker rmi devgrail-server:latest devgrail-container:latest >/dev/null 2>&1 || true
# Third-party support images pulled by the stack. Left in place unless purging,
# since they're shared/base images that may be reused.
if [ "$PURGE" = 1 ]; then
  docker rmi tecnativa/docker-socket-proxy:v0.4.2 traefik:v3.7.6 >/dev/null 2>&1 || true
fi

# --- 5. on-disk config (purge only) ----------------------------------------
if [ "$PURGE" = 1 ]; then
  log "Removing config and deploy files..."
  # $INSTALL_DIR holds the backups directory, including the one just taken —
  # say where it went before it goes, rather than silently deleting the only
  # copy of the data seconds after making it.
  if [ "$NO_BACKUP" = 0 ] && [ -d "$INSTALL_DIR/backups" ]; then
    warn "Deleting $INSTALL_DIR/backups along with the rest of $INSTALL_DIR."
    warn "If you want the pre-wipe backup, Ctrl-C now and copy it off this host."
  fi
  rm -rf "$INSTALL_DIR" "$CONFIG_DIR"
  rm -f /usr/local/bin/devgrail
  if command -v systemctl >/dev/null 2>&1; then
    systemctl disable --now devgrail-backup.timer >/dev/null 2>&1 || true
    rm -f /etc/systemd/system/devgrail-backup.timer /etc/systemd/system/devgrail-backup.service
    systemctl daemon-reload >/dev/null 2>&1 || true
  fi
fi

cat <<EOF

============================================================
  DevGrail has been uninstalled.
EOF
if [ "$PURGE" = 1 ]; then
  cat <<EOF

  Full wipe complete — data volumes, certs, and config removed.
EOF
else
  cat <<EOF

  Data preserved: the devgrail-data + traefik-acme volumes, any
  workspace volumes, and config in $INSTALL_DIR / $CONFIG_DIR
  were kept. Re-run install.sh to resume, or re-run this with
  --purge to delete everything.
EOF
fi
cat <<EOF

  Docker and jq were left installed. userns-remap in
  /etc/docker/daemon.json (if you enabled it) was left unchanged.
============================================================
EOF
