#!/usr/bin/env bash
set -Eeuo pipefail

# proxmox-subscription-popup.sh
#
# Safe Proxmox VE 9 subscription popup suppressor.
#
# Defaults:
#   - patch only
#   - no repair unless --repair is passed
#   - refuses to patch unless file matches expected structure
#
# Usage:
#   sudo bash proxmox-subscription-popup.sh
#   sudo bash proxmox-subscription-popup.sh --status
#   sudo bash proxmox-subscription-popup.sh --undo
#   sudo bash proxmox-subscription-popup.sh --repair
#
# Notes:
#   - Uses `patch` with a very specific diff
#   - Creates a backup before patching
#   - Restarts pveproxy after changes
#   - Any package update may restore the original file

SCRIPT_NAME="$(basename "$0")"
JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
STATE_DIR="/root/.proxmox-subscription-popup"
BACKUP_FILE="${STATE_DIR}/proxmoxlib.js.bak"
PATCH_FILE="${STATE_DIR}/subscription-popup.patch"
WORK_ORIG="${STATE_DIR}/proxmoxlib.js.orig"

DO_REPAIR=0
DO_UNDO=0
DO_STATUS=0

for arg in "$@"; do
  case "$arg" in
    --repair) DO_REPAIR=1 ;;
    --undo)   DO_UNDO=1 ;;
    --status) DO_STATUS=1 ;;
    -h|--help)
      cat <<'EOF'
Usage:
  sudo bash proxmox-subscription-popup.sh
      Apply the popup suppression patch

  sudo bash proxmox-subscription-popup.sh --status
      Show current state

  sudo bash proxmox-subscription-popup.sh --undo
      Restore the original file from backup

  sudo bash proxmox-subscription-popup.sh --repair
      Reinstall the package that owns proxmoxlib.js and restart pveproxy
EOF
      exit 0
      ;;
    *)
      echo "Unknown argument: $arg" >&2
      exit 2
      ;;
  esac
done

if [ "$(id -u)" -ne 0 ]; then
  echo "Please run as root." >&2
  exit 1
fi

mkdir -p "$STATE_DIR"

if [[ -t 1 ]]; then
  C_RESET=$'\033[0m'
  C_BOLD=$'\033[1m'
  C_DIM=$'\033[2m'
  C_RED=$'\033[1;31m'
  C_GREEN=$'\033[1;32m'
  C_YELLOW=$'\033[1;33m'
  C_BLUE=$'\033[1;34m'
  C_MAGENTA=$'\033[1;35m'
  C_CYAN=$'\033[1;36m'
else
  C_RESET=""
  C_BOLD=""
  C_DIM=""
  C_RED=""
  C_GREEN=""
  C_YELLOW=""
  C_BLUE=""
  C_MAGENTA=""
  C_CYAN=""
fi

say()    { printf "%b\n" "$*"; }
info()   { say "${C_CYAN}▶${C_RESET} $*"; }
ok()     { say "${C_GREEN}✔${C_RESET} $*"; }
warn()   { say "${C_YELLOW}⚠${C_RESET} $*"; }
fail()   { say "${C_RED}✖${C_RESET} $*" >&2; }
title()  { say "${C_BOLD}${C_MAGENTA}$*${C_RESET}"; }

trap 'fail "Script failed on line ${LINENO}. No further changes will be made."' ERR

restart_proxy() {
  info "Restarting pveproxy..."
  systemctl restart pveproxy
  ok "pveproxy restarted"
}

show_browser_hint() {
  say
  say "${C_BOLD}Browser refresh:${C_RESET} hard refresh the UI after this."
  say "Mac: ${C_DIM}Cmd+Shift+R${C_RESET}"
  say "Other: ${C_DIM}Ctrl+Shift+R${C_RESET}"
  say
}

detect_owner_pkg() {
  dpkg -S "$JS_FILE" 2>/dev/null | head -n1 | cut -d: -f1
}

is_proxmox() {
  command -v pveversion >/dev/null 2>&1
}

version_major() {
  if ! command -v pveversion >/dev/null 2>&1; then
    return 1
  fi
  pveversion | sed -n 's/^pve-manager\/\([0-9]\+\)\..*/\1/p' | head -n1
}

require_file() {
  [[ -f "$JS_FILE" ]] || { fail "Missing file: $JS_FILE"; exit 1; }
}

has_patch_tool() {
  command -v patch >/dev/null 2>&1
}

ensure_patch_tool() {
  if has_patch_tool; then
    return 0
  fi
  fail "'patch' command not found."
  fail "Install it with: apt-get update && apt-get install -y patch"
  exit 1
}

# Very specific to the function structure from the uploaded file.
has_expected_original() {
  grep -Fq "checked_command: function (orig_cmd) {" "$JS_FILE" &&
  grep -Fq "url: '/nodes/localhost/subscription'" "$JS_FILE" &&
  grep -Fq "res.data.status.toLowerCase() !== 'active'" "$JS_FILE" &&
  grep -Fq "title: gettext('No valid subscription')" "$JS_FILE"
}

# Detects our injected short-circuit immediately after the function opens.
is_patched() {
  python3 - <<'PY' "$JS_FILE"
import re, sys
path = sys.argv[1]
text = open(path, 'r', encoding='utf-8').read()
pat = re.compile(
    r"checked_command:\s*function\s*\(orig_cmd\)\s*\{\s*orig_cmd\(\);\s*return;\s*Proxmox\.Utils\.API2Request\(",
    re.S,
)
sys.exit(0 if pat.search(text) else 1)
PY
}

write_patch_file() {
  cat > "$PATCH_FILE" <<'EOF'
--- proxmoxlib.js.orig
+++ proxmoxlib.js
@@ -1,5 +1,7 @@
         checked_command: function (orig_cmd) {
+            orig_cmd();
+            return;
             Proxmox.Utils.API2Request({
                 url: '/nodes/localhost/subscription',
                 method: 'GET',
EOF
}

status() {
  title "Proxmox subscription popup status"

  if ! is_proxmox; then
    fail "This does not look like a Proxmox host."
    exit 1
  fi

  require_file

  local major owner
  major="$(version_major || true)"
  owner="$(detect_owner_pkg || true)"

  if [[ -n "$major" ]]; then
    info "Detected Proxmox major version: ${C_BOLD}${major}${C_RESET}"
  else
    warn "Could not determine Proxmox major version"
  fi

  if [[ -n "$owner" ]]; then
    info "Owning package: ${C_BOLD}${owner}${C_RESET}"
  else
    warn "Could not determine owning package"
  fi

  if has_expected_original; then
    ok "Expected original popup code pattern is present"
  else
    warn "Expected original popup code pattern is NOT present"
  fi

  if is_patched; then
    ok "Patch appears to be installed"
  else
    warn "Patch does not appear to be installed"
  fi

  if [[ -f "$BACKUP_FILE" ]]; then
    info "Backup present: ${C_BOLD}${BACKUP_FILE}${C_RESET}"
  else
    warn "No backup found at ${STATE_DIR}"
  fi
}

repair() {
  title "Repairing Proxmox web UI file"

  require_file

  local owner
  owner="$(detect_owner_pkg || true)"
  [[ -n "$owner" ]] || { fail "Could not determine owning package for $JS_FILE"; exit 1; }

  info "Reinstalling package: ${C_BOLD}${owner}${C_RESET}"
  apt-get update
  apt-get install --reinstall -y "$owner"

  require_file
  restart_proxy
  ok "Repair complete"
  show_browser_hint
}

undo() {
  title "Restoring original file"

  [[ -f "$BACKUP_FILE" ]] || { fail "No backup found at $BACKUP_FILE"; exit 1; }
  require_file

  cp -f "$BACKUP_FILE" "$JS_FILE"
  ok "Original file restored from backup"

  restart_proxy
  show_browser_hint
}

apply_patch() {
  title "Applying safe Proxmox popup patch"

  require_file
  ensure_patch_tool

  if ! is_proxmox; then
    fail "This does not look like a Proxmox host."
    exit 1
  fi

  local major
  major="$(version_major || true)"
  if [[ "$major" == "9" ]]; then
    ok "Detected Proxmox VE 9"
  else
    warn "Expected Proxmox VE 9, detected: ${major:-unknown}"
    warn "Proceeding only if the file matches the exact expected structure"
  fi

  if is_patched; then
    ok "Patch is already installed. Nothing to do."
    show_browser_hint
    return 0
  fi

  if ! has_expected_original; then
    fail "The expected original code pattern was not found."
    fail "Refusing to patch, because the file layout is not what this script expects."
    fail "Use --repair first if the file is damaged, then try again."
    exit 1
  fi
  ok "Expected original code pattern found"

  if [[ ! -f "$BACKUP_FILE" ]]; then
    info "Creating backup at ${C_BOLD}${BACKUP_FILE}${C_RESET}"
    cp -a "$JS_FILE" "$BACKUP_FILE"
    ok "Backup created"
  else
    warn "Backup already exists, leaving it untouched"
  fi

  cp -a "$JS_FILE" "$WORK_ORIG"
  write_patch_file

  info "Testing patch with dry-run..."
  if ! (cd "$STATE_DIR" && patch --dry-run proxmoxlib.js.orig subscription-popup.patch >/dev/null 2>&1); then
    fail "Patch dry-run failed."
    fail "No changes were made."
    exit 1
  fi
  ok "Patch dry-run succeeded"

  info "Applying patch..."
  cp -a "$JS_FILE" "$WORK_ORIG"
  if ! (cd "$STATE_DIR" && patch "$WORK_ORIG" subscription-popup.patch >/dev/null 2>&1); then
    fail "Patch command failed unexpectedly."
    exit 1
  fi

  cp -f "$WORK_ORIG" "$JS_FILE"

  if ! is_patched; then
    warn "Verification failed after patch; restoring backup"
    cp -f "$BACKUP_FILE" "$JS_FILE"
    fail "Patch did not verify cleanly. Original file restored."
    exit 1
  fi

  ok "Patch verified successfully"
  restart_proxy
  ok "Subscription popup suppression is in place"
  show_browser_hint
}

case 1 in
  $DO_STATUS) status ;;
  $DO_UNDO) undo ;;
  $DO_REPAIR) repair ;;
  *) apply_patch ;;
esac