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

APP_NAME="pi-car-companion"
INSTALL_ROOT="/opt/${APP_NAME}"
REPOSITORY="${INSTALL_ROOT}/repository"
RELEASES="${INSTALL_ROOT}/releases"
CURRENT="${INSTALL_ROOT}/current"
STAGING_ROOT="${INSTALL_ROOT}/staging"
CONFIG_DIR="/etc/${APP_NAME}"
DATA_DIR="/var/lib/${APP_NAME}"
STATUS_FILE="${DATA_DIR}/update-status.json"
BRANCH_FILE="${CONFIG_DIR}/update-branch"
UPDATE_USER_FILE="${CONFIG_DIR}/update-user"
LOCK_FILE="/run/lock/${APP_NAME}-update.lock"
WORKTREE=""
RELEASE_TMP=""
FROM_REVISION=""
TO_REVISION=""
PHASE="checking for updates"

if [[ ${EUID} -ne 0 ]]; then
  echo "The update helper must run as root." >&2
  exit 1
fi

write_status() {
  local state="$1"
  local message="$2"
  local update_group_id
  update_group_id="$(getent group pi-companion | cut -d: -f3)"
  UPDATE_STATE="${state}" \
  UPDATE_MESSAGE="${message}" \
  UPDATE_FROM="${FROM_REVISION}" \
  UPDATE_TO="${TO_REVISION}" \
  UPDATE_STATUS_FILE="${STATUS_FILE}" \
  UPDATE_GROUP_ID="${update_group_id}" \
    /usr/bin/node --input-type=module -e '
      import { chmodSync, chownSync, renameSync, writeFileSync } from "node:fs";
      const target = process.env.UPDATE_STATUS_FILE;
      const temporary = `${target}.tmp`;
      const value = {
        state: process.env.UPDATE_STATE,
        message: process.env.UPDATE_MESSAGE,
        fromRevision: process.env.UPDATE_FROM || null,
        toRevision: process.env.UPDATE_TO || null,
        updatedAt: new Date().toISOString()
      };
      writeFileSync(temporary, `${JSON.stringify(value)}\n`, { mode: 0o644 });
      chownSync(temporary, 0, Number(process.env.UPDATE_GROUP_ID));
      chmodSync(temporary, 0o644);
      renameSync(temporary, target);
    '
}

install_integration_files() {
  local source_release="$1"
  install -m 0755 "${source_release}/scripts/pi-car-companion-update" /usr/local/sbin/pi-car-companion-update
  install -m 0755 "${source_release}/scripts/pi-car-companion-network-setup" /usr/local/sbin/pi-car-companion-network-setup
  install -m 0644 "${source_release}/systemd/pi-car-companion.service" /etc/systemd/system/pi-car-companion.service
  install -m 0644 "${source_release}/systemd/pi-car-companion-update.service" /etc/systemd/system/pi-car-companion-update.service
  install -m 0644 "${source_release}/systemd/pi-car-companion-network.service" /etc/systemd/system/pi-car-companion-network.service
  install -m 0644 "${source_release}/systemd/pi-car-companion-network-configure.service" /etc/systemd/system/pi-car-companion-network-configure.service
  install -m 0644 "${source_release}/systemd/pi-car-companion-network-observer.service" /etc/systemd/system/pi-car-companion-network-observer.service
  install -m 0440 "${source_release}/systemd/pi-car-companion.sudoers" /etc/sudoers.d/pi-car-companion
  install -d -m 0755 /etc/NetworkManager/dnsmasq-shared.d
  install -m 0644 "${source_release}/config/dnsmasq-shared-pi-car-companion.conf" /etc/NetworkManager/dnsmasq-shared.d/pi-car-companion.conf
}

cleanup() {
  if [[ -n "${WORKTREE}" && "${WORKTREE}" == "${STAGING_ROOT}/"* && -e "${WORKTREE}" ]]; then
    git -C "${REPOSITORY}" worktree remove --force "${WORKTREE}" >/dev/null 2>&1 || rm -rf -- "${WORKTREE}"
  fi
  if [[ -n "${RELEASE_TMP}" && "${RELEASE_TMP}" == "${RELEASES}/.new-"* && -e "${RELEASE_TMP}" ]]; then
    rm -rf -- "${RELEASE_TMP}"
  fi
}

on_error() {
  local exit_code=$?
  trap - ERR
  cleanup
  write_status "failed" "Update failed while ${PHASE}. The previous version is still active."
  exit "${exit_code}"
}
trap on_error ERR
trap cleanup EXIT

exec 9>"${LOCK_FILE}"
if ! flock -n 9; then
  write_status "failed" "Another update is already running."
  exit 1
fi

if [[ ! -d "${REPOSITORY}/.git" || ! -f "${BRANCH_FILE}" || ! -f "${UPDATE_USER_FILE}" ]]; then
  write_status "failed" "The deployment repository is not configured. Run install.sh again."
  exit 1
fi

FROM_REVISION="$(cat "${CURRENT}/REVISION" 2>/dev/null || true)"
branch="$(tr -d '\r\n' < "${BRANCH_FILE}")"
update_user="$(tr -d '\r\n' < "${UPDATE_USER_FILE}")"
if ! id "${update_user}" >/dev/null 2>&1; then
  write_status "failed" "The Git update user no longer exists. Run install.sh again."
  exit 1
fi
write_status "checking" "Checking the configured Git remote for updates."

chown -R "${update_user}":"$(id -gn "${update_user}")" "${REPOSITORY}/.git"
if [[ "${update_user}" == "root" ]]; then
  git -C "${REPOSITORY}" fetch --prune origin
else
  sudo -H -u "${update_user}" -- git -C "${REPOSITORY}" fetch --prune origin
fi
TO_REVISION="$(git -C "${REPOSITORY}" rev-parse "origin/${branch}")"

if [[ -n "${FROM_REVISION}" && "${FROM_REVISION}" == "${TO_REVISION}" ]]; then
  install_integration_files "${CURRENT}"
  visudo -cf /etc/sudoers.d/pi-car-companion >/dev/null
  systemctl daemon-reload
  write_status "current" "Pi Car Companion is already up to date."
  exit 0
fi

if [[ -n "${FROM_REVISION}" ]] && ! git -C "${REPOSITORY}" merge-base --is-ancestor "${FROM_REVISION}" "${TO_REVISION}"; then
  write_status "failed" "The remote update is not a fast-forward change. Automatic update was refused."
  exit 1
fi

PHASE="building the new release"
write_status "building" "Downloading dependencies and verifying the new release."
install -d -m 0755 "${STAGING_ROOT}" "${RELEASES}"
WORKTREE="${STAGING_ROOT}/worktree-${TO_REVISION}-$$"
git -C "${REPOSITORY}" worktree add --detach "${WORKTREE}" "${TO_REVISION}"

cd "${WORKTREE}"
npm ci
npm run check
printf '%s\n' "${TO_REVISION}" > REVISION

PHASE="activating the new release"
RELEASE_TMP="${RELEASES}/.new-${TO_REVISION}-$$"
install -d -m 0755 "${RELEASE_TMP}"
rsync -a --delete --exclude .git "${WORKTREE}/" "${RELEASE_TMP}/"
release_final="${RELEASES}/${TO_REVISION}"
if [[ -e "${release_final}" ]]; then
  rm -rf -- "${release_final}"
fi
mv "${RELEASE_TMP}" "${release_final}"
RELEASE_TMP=""
chown -R root:root "${release_final}"

install_integration_files "${release_final}"
visudo -cf /etc/sudoers.d/pi-car-companion >/dev/null
systemctl daemon-reload

ln -sfn "releases/${TO_REVISION}" "${INSTALL_ROOT}/current.next"
mv -Tf "${INSTALL_ROOT}/current.next" "${CURRENT}"
PHASE="verifying the updated service"
write_status "building" "The update is installed. Verifying the restarted service."
healthy=false
if systemctl restart pi-car-companion.service; then
  for _attempt in {1..30}; do
    if curl --fail --silent --show-error http://127.0.0.1:8787/healthz >/dev/null; then
      healthy=true
      break
    fi
    sleep 1
  done
fi

if [[ "${healthy}" != "true" ]]; then
  if [[ -n "${FROM_REVISION}" && -d "${RELEASES}/${FROM_REVISION}" ]]; then
    install_integration_files "${RELEASES}/${FROM_REVISION}"
    systemctl daemon-reload
    ln -sfn "releases/${FROM_REVISION}" "${INSTALL_ROOT}/current.next"
    mv -Tf "${INSTALL_ROOT}/current.next" "${CURRENT}"
    systemctl restart pi-car-companion.service || true
    write_status "failed" "The new release failed its health check. The previous release was restored."
  else
    write_status "failed" "The new release failed its health check and no previous release was available."
  fi
  exit 1
fi

write_status "success" "Update installed and the companion service is healthy."
systemctl try-restart pi-car-companion-network.service || true
systemctl enable pi-car-companion-network-observer.service || true
systemctl restart pi-car-companion-network-observer.service || true
