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

# Appmint hub agent — macOS installer.
#
# Installs Node (via Homebrew) if missing, drops the agent under
# /opt/appmint-hub, registers as a LaunchDaemon (boots on machine startup),
# runs the setup wizard, and starts the service.
#
# Usage:
#   curl -sSL https://appmint.io/hub-mac | sudo bash
# or for a local clone:
#   sudo ./scripts/install-mac.sh
#
# Re-run upgrades the binary in place; config is preserved.

INSTALL_DIR="/opt/appmint-hub"
CONFIG_DIR="/etc/appmint-hub"
PLIST_LABEL="io.appmint.hub"
# Pinned at publish time by deploy.js — see the note in install.sh.
# Override to install a specific release: HUB_VERSION=0.1.0 sudo ./install-mac.sh
HUB_VERSION="${HUB_VERSION:-dev}"
RELEASE_BASE_URL="${RELEASE_BASE_URL:-__RELEASE_BASE_URL__}"
# LaunchAgent (user GUI session), NOT LaunchDaemon — see plist header for why.
# PLIST_TARGET resolved against the install user's home in install_service().
NODE_MAJOR="20"

require_root() {
  if [ "${EUID}" -ne 0 ]; then
    echo "install: must run as root (use sudo)" >&2
    exit 1
  fi
}

ensure_brew_or_node() {
  # Homebrew lives under /opt/homebrew on Apple Silicon, /usr/local on Intel.
  if ! command -v brew >/dev/null 2>&1; then
    if [ -x /opt/homebrew/bin/brew ]; then
      export PATH="/opt/homebrew/bin:$PATH"
    elif [ -x /usr/local/bin/brew ]; then
      export PATH="/usr/local/bin:$PATH"
    fi
  fi

  if command -v node >/dev/null 2>&1; then
    local current
    current="$(node -v | sed 's/^v//' | cut -d. -f1)"
    if [ "${current}" -ge "${NODE_MAJOR}" ]; then
      return
    fi
  fi

  if command -v brew >/dev/null 2>&1; then
    echo "install: installing Node ${NODE_MAJOR} via Homebrew…"
    sudo -u "${SUDO_USER:-$(whoami)}" brew install "node@${NODE_MAJOR}" 2>/dev/null || brew install "node@${NODE_MAJOR}"
    brew link --force --overwrite "node@${NODE_MAJOR}" >/dev/null 2>&1 || true
  else
    echo "install: Homebrew not found and Node ${NODE_MAJOR}+ missing." >&2
    echo "         Install Homebrew first: https://brew.sh" >&2
    echo "         Or install Node ${NODE_MAJOR}+ manually, then re-run this script." >&2
    exit 1
  fi
}

install_agent() {
  echo "install: copying agent to ${INSTALL_DIR}…"
  mkdir -p "${INSTALL_DIR}"
  local src
  src="$(cd "$(dirname "$0")/.." && pwd)"
  if [ ! -f "${src}/package.json" ] || ! grep -q '"name": "hub-agent"' "${src}/package.json"; then
    install_agent_from_release
    return
  fi
  rsync -a --delete \
    --exclude=node_modules --exclude=dist \
    "${src}/" "${INSTALL_DIR}/"
  cd "${INSTALL_DIR}"
  # Install full deps (TS + @types live in devDependencies and are needed
  # to compile), build, then prune dev deps so production runtime is lean.
  npm ci
  npm run build
  npm prune --omit=dev
}

# Download the published tarball. It already contains a compiled dist/, so the
# client machine never needs TypeScript — only the two runtime dependencies.
install_agent_from_release() {
  if [ "${RELEASE_BASE_URL}" = "__RELEASE_BASE_URL__" ]; then
    echo "install: this installer was not published (no release URL baked in)." >&2
    echo "         Run from a local clone: cd /path/to/websitemint/apps/hub-agent && sudo ./scripts/install-mac.sh" >&2
    exit 1
  fi

  local tarball="hub-agent-${HUB_VERSION}.tar.gz"
  local tmp
  tmp="$(mktemp -d)"
  trap 'rm -rf "${tmp}"' RETURN

  echo "install: downloading hub-agent ${HUB_VERSION}…"
  curl -fsSL "${RELEASE_BASE_URL}/${tarball}" -o "${tmp}/${tarball}"

  # Verified for the same reason as the Linux installer: this runs as root
  # straight off a URL, so a swapped tarball must stop the install.
  # macOS ships `shasum`, not `sha256sum`.
  if curl -fsSL "${RELEASE_BASE_URL}/release.json" -o "${tmp}/release.json" 2>/dev/null; then
    local want have
    want="$(sed -n 's/.*"sha256"[[:space:]]*:[[:space:]]*"\([a-f0-9]*\)".*/\1/p' "${tmp}/release.json")"
    have="$(shasum -a 256 "${tmp}/${tarball}" | cut -d' ' -f1)"
    if [ -n "${want}" ] && [ "${want}" != "${have}" ]; then
      echo "install: checksum mismatch for ${tarball} — refusing to install." >&2
      echo "         expected ${want}" >&2
      echo "         got      ${have}" >&2
      exit 1
    fi
  fi

  tar -xzf "${tmp}/${tarball}" -C "${tmp}"
  rsync -a --delete --exclude=node_modules "${tmp}/hub-agent/" "${INSTALL_DIR}/"
  cd "${INSTALL_DIR}"
  npm install --omit=dev --no-audit --no-fund
}

pick_run_user() {
  # The daemon runs as the user who installed it (the one who ran sudo).
  # macOS CUPS is per-user — the printers added in System Settings live
  # in this user's view, and the daemon needs to share that view.
  RUN_USER="${SUDO_USER:-}"
  if [ -z "${RUN_USER}" ] || [ "${RUN_USER}" = "root" ]; then
    echo "install: must be invoked with sudo from a regular user account." >&2
    echo "         (Don't log in as root and run this directly.)" >&2
    exit 1
  fi
}

install_service() {
  echo "install: dropping LaunchAgent plist…"
  mkdir -p "${CONFIG_DIR}"

  # LaunchAgent installs under the install user's home and loads into their
  # GUI session so it shares the user's CUPS printers (Aqua session). The
  # alternative — a LaunchDaemon in /Library/LaunchDaemons/ — runs in the
  # system session and CANNOT use user-installed printers, even with
  # UserName set.
  local home_dir
  home_dir="$(eval echo "~${RUN_USER}")"
  local agents_dir="${home_dir}/Library/LaunchAgents"
  # Global (no `local`) so print_summary can reference it after this runs.
  LOG_DIR="${home_dir}/Library/Logs"
  PLIST_TARGET="${agents_dir}/${PLIST_LABEL}.plist"

  sudo -u "${RUN_USER}" mkdir -p "${agents_dir}" "${LOG_DIR}"

  local node_bin
  node_bin="$(command -v node)"
  sed -e "s|/usr/local/bin/node|${node_bin}|g" \
      -e "s|__HOME__|${home_dir}|g" \
    "${INSTALL_DIR}/launchd/${PLIST_LABEL}.plist" > "${PLIST_TARGET}"
  chown "${RUN_USER}" "${PLIST_TARGET}"
  chmod 0644 "${PLIST_TARGET}"

  # Logs land in ~/Library/Logs — owned by the user, no special permissions.
  sudo -u "${RUN_USER}" touch "${LOG_DIR}/appmint-hub.out.log" "${LOG_DIR}/appmint-hub.err.log"

  # Config dir at /etc/appmint-hub — user needs to be able to write it for
  # WS-pushed config updates from the admin UI.
  chown -R "${RUN_USER}" "${CONFIG_DIR}"

  echo "install: LaunchAgent installed at ${PLIST_TARGET}"
  echo "install: agent will run as user '${RUN_USER}' in their GUI session"
  echo "install: logs at ${LOG_DIR}/appmint-hub.{out,err}.log"
}

run_wizard_if_needed() {
  if [ ! -f "${CONFIG_DIR}/config.json" ]; then
    echo "install: no existing config — running setup wizard"
    node "${INSTALL_DIR}/dist/setup.js" --out="${CONFIG_DIR}/config.json"
    return
  fi

  echo ""
  echo "Existing config found at ${CONFIG_DIR}/config.json:"
  node -e "const c=require('${CONFIG_DIR}/config.json'); console.log('  hub:    '+c.hubName); console.log('  org:    '+c.orgId); console.log('  server: '+c.serverUrl);" 2>/dev/null || true
  echo ""
  echo "  1) Keep existing config — just update the software  [default]"
  echo "  2) Reconfigure — change credentials / server URL"
  echo ""
  printf "Choose 1 or 2 [1]: "
  read choice
  choice="${choice:-1}"
  if [ "$choice" = "2" ]; then
    node "${INSTALL_DIR}/dist/setup.js" --out="${CONFIG_DIR}/config.json"
  else
    echo "install: keeping existing config — software-only update"
  fi
}

install_cli_wrapper() {
  # Drop a tiny wrapper at /usr/local/bin/hub-agent so the operator can
  # type `sudo hub-agent status` instead of `sudo node /opt/.../cli.js status`.
  local wrapper="/usr/local/bin/hub-agent"
  echo "install: dropping CLI wrapper at ${wrapper}"
  mkdir -p /usr/local/bin
  cat > "${wrapper}" <<EOF
#!/usr/bin/env bash
exec node "${INSTALL_DIR}/dist/cli.js" "\$@"
EOF
  chmod +x "${wrapper}"
}

start_service() {
  # LaunchAgent — load into the install user's GUI domain. We also unload
  # any leftover system-domain LaunchDaemon from a previous install so we
  # don't end up with two copies fighting for the WS slot.
  local uid
  uid="$(id -u "${RUN_USER}")"

  if [ -f "/Library/LaunchDaemons/${PLIST_LABEL}.plist" ]; then
    echo "install: removing legacy LaunchDaemon from previous install…"
    launchctl bootout "system/${PLIST_LABEL}" 2>/dev/null || true
    rm -f "/Library/LaunchDaemons/${PLIST_LABEL}.plist"
  fi

  # Bootstrap into the GUI session. -gui is the domain; uid maps to "this
  # user's GUI session". Re-bootstrap if already loaded so plist changes
  # take effect.
  launchctl bootout "gui/${uid}/${PLIST_LABEL}" 2>/dev/null || true
  echo "install: bootstrapping LaunchAgent for uid=${uid} (${RUN_USER})…"
  launchctl bootstrap "gui/${uid}" "${PLIST_TARGET}"
  launchctl enable "gui/${uid}/${PLIST_LABEL}"
  launchctl kickstart -k "gui/${uid}/${PLIST_LABEL}"
  sleep 1
}

print_summary() {
  echo ""
  echo "=========================================================="
  echo "  appmint-hub installed and RUNNING"
  echo "=========================================================="
  echo ""
  echo "Service:   ${PLIST_LABEL}  (launchd)"
  echo "Status:    sudo launchctl list ${PLIST_LABEL}"
  echo "Auto-start: yes — will start automatically on every boot."
  echo ""
  echo "Files:"
  echo "  install:  ${INSTALL_DIR}"
  echo "  config:   ${CONFIG_DIR}/config.json"
  echo "  logs:     ${LOG_DIR:-$HOME/Library/Logs}/appmint-hub.out.log"
  echo "            ${LOG_DIR:-$HOME/Library/Logs}/appmint-hub.err.log"
  echo ""
  echo "Commands (run from anywhere):"
  echo "  sudo hub-agent status        Connection state + config + recent log"
  echo "  sudo hub-agent logs -f       Follow the log live"
  echo "  sudo hub-agent devices       List configured + USB-detected devices"
  echo "  sudo hub-agent setup         Reconfigure credentials / server URL"
  echo "  sudo hub-agent update        Update software (preserves config)"
  echo "  sudo hub-agent restart       Bounce the service"
  echo "  sudo hub-agent help          Full command list"
  echo ""
  echo "Verify it's connected:  sudo hub-agent status"
  echo "Or check the admin UI:  Operations → Devices → Hubs"
  echo ""
}

main() {
  require_root
  pick_run_user
  ensure_brew_or_node
  install_agent
  install_service
  install_cli_wrapper
  run_wizard_if_needed
  start_service
  print_summary
}

main "$@"
