#!/bin/sh
# Perstat Agent uninstaller (Linux/macOS). Removes the agent without residue.
#
#   curl -fsSL https://agent.perstat.io/uninstall.sh | sudo sh
#
# Steps:
#   1. Deregister from the cockpit (best-effort, using the credentials in the
#      config file) so the host disappears there automatically.
#   2. Stop + remove the service (launchd/systemd).
#   3. Remove the binary, state, dedicated service user (Linux) and logs.
#
# BOTH layouts are always handled: the current perstat-* one that install.sh
# writes, and the datargo-* one from before the rename. A host enrolled back
# then keeps those paths forever, because a self-update only swaps the binary
# inside the existing directory and can change neither unit nor paths (same
# reason config.rs keeps LEGACY_STATE_PATH readable). An uninstaller that knows
# only one layout finds nothing on the other, and since every removal here is
# failure-tolerant by design, it would exit 0 while the service keeps running.
#
# Safety: NO `rm -rf` on system paths, known files are removed individually and
# the (now empty) state directory with `rmdir` (which never recurses). Idempotent.
set -eu

# Layouts, newest first: "<slug> <launchd-label>". Everything else is derived
# from the slug, exactly as install.sh composes it.
LAYOUTS="perstat-agent:io.perstat.agent datargo-agent:com.datargo.agent"

info() { printf '\033[1;34m›\033[0m %s\n' "$1"; }
ok() { printf '\033[1;32m✓\033[0m %s\n' "$1"; }
die() { printf '\033[1;31m✗ %s\033[0m\n' "$1" >&2; exit 1; }

SUDO_CMD=""
if [ "$(id -u)" -ne 0 ]; then
  command -v sudo >/dev/null 2>&1 && SUDO_CMD="sudo" || die "root/sudo required to remove."
fi

os="$(uname -s)"
info "Removing Perstat Agent ($os) …"

# Did we actually find anything? Every removal below tolerates absence, so
# without this the script reports success on a host it never touched.
FOUND=0
DEREGISTERED=""

# Read one value out of the agent's TOML state. Through sudo on purpose: the
# state dir is 0750 and owned by the service user, so an unprivileged run reads
# nothing and would skip the deregistration in silence.
state_get() {
  $SUDO_CMD sed -n 's/^[[:space:]]*'"$2"'[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$1" 2>/dev/null | head -1
}

# ── 1. Deregister from the cockpit (best-effort) ─────────────────────────────
# Read base URL + per-agent token from the config; revoke this agent server-side
# so it vanishes from the cockpit. Never blocks the uninstall (host may be off).
deregister() {
  state_file="$1"
  $SUDO_CMD test -r "$state_file" 2>/dev/null || return 0
  command -v curl >/dev/null 2>&1 || return 0
  server="$(state_get "$state_file" server)"
  token="$(state_get "$state_file" agent_token)"
  [ -n "$server" ] && [ -n "$token" ] || return 0
  # Both layouts can carry the SAME identity (a host from before the rename
  # keeps its old state file even after the new installer ran), and the second
  # DELETE on an already-revoked token would report a bogus failure.
  case " $DEREGISTERED " in
    *" $token "*) return 0 ;;
  esac
  DEREGISTERED="$DEREGISTERED $token"
  info "Deregistering from the cockpit ($state_file) …"
  if curl -fsS --max-time 15 -X DELETE -H "Authorization: Bearer $token" "$server/agent/self" >/dev/null 2>&1; then
    ok "Removed from the cockpit."
  else
    info "Could not reach the cockpit, remove the entry manually there (Host agents → Remove)."
  fi
}

# ── 2. Stop + remove the service ─────────────────────────────────────────────
remove_service() {
  slug="$1"
  label="$2"
  plist="/Library/LaunchDaemons/$label.plist"
  unit="/etc/systemd/system/$slug.service"
  log="/var/log/$slug.log"
  case "$os" in
    Darwin)
      if [ -f "$plist" ]; then
        FOUND=1
        info "Removing service $label …"
      fi
      $SUDO_CMD launchctl bootout "system/$label" 2>/dev/null || true
      $SUDO_CMD rm -f "$plist"
      $SUDO_CMD rm -f "$log"
      ;;
    Linux)
      if [ -f "$unit" ]; then
        FOUND=1
        info "Removing service $slug …"
      fi
      if command -v systemctl >/dev/null 2>&1; then
        $SUDO_CMD systemctl disable --now "$slug" 2>/dev/null || true
      fi
      $SUDO_CMD rm -f "$unit"
      command -v systemctl >/dev/null 2>&1 && $SUDO_CMD systemctl daemon-reload 2>/dev/null || true
      # Remove the dedicated service user (if present).
      if id "$slug" >/dev/null 2>&1; then
        FOUND=1
        $SUDO_CMD userdel "$slug" 2>/dev/null || $SUDO_CMD deluser "$slug" 2>/dev/null || true
      fi
      ;;
    *)
      info "Unknown OS, removing only binary + state."
      ;;
  esac
}

# ── 3. Remove binary + state (no rm -rf) ─────────────────────────────────────
remove_files() {
  slug="$1"
  # Defensive: every path below is composed from the slug, an empty one would
  # turn `rmdir /opt/$slug` into `rmdir /opt`.
  [ -n "$slug" ] || die "internal error: empty layout slug."
  install_root="/opt/$slug"
  bin_dir="/opt/$slug/bin"
  bin="/opt/$slug/bin/$slug"
  state_dir="/var/lib/$slug"
  state_file="/var/lib/$slug/agent.toml"
  # `[ -e … ] && FOUND=1` would abort the whole script under `set -e` whenever
  # the file is absent, which is the normal case for one of the two layouts.
  if [ -e "$bin" ]; then
    FOUND=1
  fi
  $SUDO_CMD rm -f "$bin"
  # Bin dir + install root: rmdir only (never recurses; leaves non-empty dirs).
  $SUDO_CMD rmdir "$bin_dir" 2>/dev/null || true
  $SUDO_CMD rmdir "$install_root" 2>/dev/null || true
  # Known state files individually, then rmdir (only removes an EMPTY directory and
  # never recurses, unexpected files are left untouched).
  if $SUDO_CMD test -e "$state_file" 2>/dev/null; then
    FOUND=1
  fi
  $SUDO_CMD rm -f "$state_file"
  if $SUDO_CMD test -d "$state_dir" 2>/dev/null; then
    $SUDO_CMD rmdir "$state_dir" 2>/dev/null \
      || info "$state_dir not empty, left in place (inspect manually)."
  fi
}

for layout in $LAYOUTS; do
  slug="${layout%%:*}"
  label="${layout##*:}"
  deregister "/var/lib/$slug/agent.toml"
  remove_service "$slug" "$label"
  remove_files "$slug"
done

if [ "$FOUND" = "1" ]; then
  ok "Perstat Agent removed."
else
  info "No Perstat Agent found under /opt/perstat-agent or /opt/datargo-agent."
  info "Nothing removed. A custom INSTALL_DIR has to be cleaned up by hand."
fi
