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

STATE_DIR="${BRZ_RECOVERY_STATE_DIR:-/var/lib/brz-healthos/recovery}"
CACHE_DIR="${BRZ_RECOVERY_CACHE_DIR:-/var/cache/brz-healthos/packages}"
CONFIG="${BRZ_RECOVERY_CONFIG:-/etc/brz-healthos/recovery.conf}"
MAX_BOOT_ATTEMPTS=3
MIN_FREE_MB=1024
MANAGED_PACKAGES=(brz-healthos-desktop brz-health-agent)
OS_PACKAGE_RECORD="$STATE_DIR/previous-os-packages"
OS_SNAPSHOT_RECORD="$STATE_DIR/os-snapshot"

if [[ -r "$CONFIG" ]]; then
  # This root-owned file contains simple administrator settings.
  # shellcheck disable=SC1090
  . "$CONFIG"
fi

log() { printf '%s %s\n' "$(date --iso-8601=seconds)" "$*" | tee -a "$STATE_DIR/recovery.log"; }
need_root() { [[ $EUID -eq 0 ]] || { echo "Run as root." >&2; exit 1; }; }
init_dirs() {
  install -d -m 0700 "$STATE_DIR"
  install -d -m 0750 "$CACHE_DIR"
  touch "$STATE_DIR/recovery.log"
  chmod 0600 "$STATE_DIR/recovery.log"
}
package_version() { dpkg-query -W -f='${Version}' "$1" 2>/dev/null || true; }
snapshot_capable() { command -v snapper >/dev/null 2>&1 && findmnt -n -o FSTYPE / | grep -qx btrfs; }

record_os_state() {
  dpkg-query -W -f='${binary:Package}\t${Version}\n' > "$OS_PACKAGE_RECORD"
  chmod 0600 "$OS_PACKAGE_RECORD"
  printf '0\n' > "$STATE_DIR/boot-attempts"
  if snapshot_capable; then
    local number
    number="$(snapper create --type single --print-number --description 'BRZ pre-update')"
    [[ "$number" =~ ^[0-9]+$ ]] || { log "Snapshot creation failed"; return 1; }
    printf '%s\n' "$number" > "$OS_SNAPSHOT_RECORD"
  fi
}

preflight_os() {
  need_root; init_dirs
  dpkg --audit | grep -q . && { log "Refusing update: dpkg has incomplete transactions"; return 1; }
  local free_mb
  free_mb="$(df -Pm / | awk 'NR==2 {print $4}')"
  [[ "$free_mb" =~ ^[0-9]+$ ]] && (( free_mb >= 8192 )) || { log "Refusing update: at least 8 GB free is required"; return 1; }
  systemctl is-active --quiet brz-health-agent.service || { log "Refusing update: agent is not healthy"; return 1; }
}

apply_os_updates() {
  preflight_os; record_os_state
  log "Applying Ubuntu package updates"
  # BRZ packages are updated through their separately signed HealthOS workflow.
  # Holding them here prevents apt from restarting the reporting agent midway.
  apt-mark hold "${MANAGED_PACKAGES[@]}" >/dev/null
  DEBIAN_FRONTEND=noninteractive apt-get update
  if ! DEBIAN_FRONTEND=noninteractive apt-get -y upgrade; then
    apt-mark unhold "${MANAGED_PACKAGES[@]}" >/dev/null || true
    return 1
  fi
  apt-mark unhold "${MANAGED_PACKAGES[@]}" >/dev/null
  date --iso-8601=seconds > "$STATE_DIR/os-update-pending"
  log "Ubuntu package update completed; reboot and health confirmation remain required"
}

rollback_os() {
  need_root; init_dirs
  if [[ -s "$OS_SNAPSHOT_RECORD" ]] && snapshot_capable; then
    local number
    read -r number < "$OS_SNAPSHOT_RECORD"
    [[ "$number" =~ ^[0-9]+$ ]] || return 2
    snapper rollback "$number"
    log "Btrfs rollback prepared; reboot required"
    return 0
  fi
  [[ -s "$OS_PACKAGE_RECORD" ]] || { log "No OS rollback state exists"; return 2; }
  local pkg version candidate
  local -a debs=()
  while IFS=$'\t' read -r pkg version; do
    candidate="$(find /var/cache/apt/archives -maxdepth 1 -type f -name "${pkg%%:*}_${version}_*.deb" -print -quit 2>/dev/null || true)"
    [[ -n "$candidate" ]] && debs+=("$candidate")
  done < "$OS_PACKAGE_RECORD"
  (( ${#debs[@]} > 0 )) || { log "Rollback unavailable: no previous packages remain in the APT cache"; return 3; }
  DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-downgrades "${debs[@]}"
  log "Cached Ubuntu packages restored; reboot and validation are required"
}

release_upgrade() {
  local target="${1:-}"
  [[ "$target" =~ ^[0-9]{2}\.04$ ]] || { log "Target must be an Ubuntu LTS release"; return 2; }
  preflight_os
  snapshot_capable || { log "Refusing release upgrade: full Btrfs/snapper rollback is not available"; return 3; }
  record_os_state
  local current
  current="$(. /etc/os-release; printf '%s' "$VERSION_ID")"
  [[ "$target" > "$current" ]] || { log "Target $target is not newer than $current"; return 4; }
  local current_year target_year expected
  current_year="${current%%.*}"; target_year="${target%%.*}"
  expected="$((10#$current_year + 2)).04"
  [[ "$target" == "$expected" ]] || { log "Refusing skipped LTS upgrade: next supported target is $expected"; return 5; }
  log "Starting sequential Ubuntu LTS upgrade from $current toward $target"
  DEBIAN_FRONTEND=noninteractive do-release-upgrade -f DistUpgradeViewNonInteractive
  date --iso-8601=seconds > "$STATE_DIR/os-update-pending"
  log "Release upgrade completed; reboot and health confirmation remain required"
}

health_check() {
  local failures=0 free_mb pkg
  dpkg --audit | grep -q . && { echo "FAIL: dpkg has incomplete transactions"; failures=$((failures + 1)); } || true
  free_mb="$(df -Pm / | awk 'NR==2 {print $4}')"
  if [[ ! "$free_mb" =~ ^[0-9]+$ ]] || (( free_mb < MIN_FREE_MB )); then
    echo "FAIL: root filesystem has ${free_mb:-unknown} MB free (minimum $MIN_FREE_MB)"
    failures=$((failures + 1))
  fi
  for pkg in "${MANAGED_PACKAGES[@]}"; do
    dpkg-query -W -f='${db:Status-Status}' "$pkg" 2>/dev/null | grep -qx 'installed' || {
      echo "FAIL: $pkg is not fully installed"
      failures=$((failures + 1))
    }
  done
  systemctl is-active --quiet brz-health-agent.service || {
    echo "FAIL: brz-health-agent.service is not active"
    failures=$((failures + 1))
  }
  if (( failures == 0 )); then echo "OK: BRZ HealthOS core is healthy"; fi
  return "$failures"
}

cache_installed() {
  local pkg version candidate
  for pkg in "${MANAGED_PACKAGES[@]}"; do
    version="$(package_version "$pkg")"
    [[ -n "$version" ]] || continue
    candidate="$(find /var/cache/apt/archives "$CACHE_DIR" -maxdepth 1 -type f -name "${pkg}_${version}_*.deb" -print -quit 2>/dev/null || true)"
    if [[ -n "$candidate" ]]; then
      install -m 0640 "$candidate" "$CACHE_DIR/$(basename "$candidate")"
    else
      log "WARNING: no cached .deb found for $pkg $version"
    fi
  done
}

prepare_update() {
  need_root; init_dirs; cache_installed
  : > "$STATE_DIR/previous-packages"
  local pkg version
  for pkg in "${MANAGED_PACKAGES[@]}"; do
    version="$(package_version "$pkg")"
    [[ -n "$version" ]] && printf '%s\t%s\n' "$pkg" "$version" >> "$STATE_DIR/previous-packages"
  done
  printf '0\n' > "$STATE_DIR/boot-attempts"
  date --iso-8601=seconds > "$STATE_DIR/update-pending"
  log "Prepared recovery state for BRZ package update"
}

rollback_packages() {
  need_root; init_dirs
  [[ -s "$STATE_DIR/previous-packages" ]] || { log "No previous package set is recorded"; return 2; }
  local pkg version deb
  local -a debs=()
  while IFS=$'\t' read -r pkg version; do
    [[ " ${MANAGED_PACKAGES[*]} " == *" $pkg "* ]] || continue
    deb="$(find "$CACHE_DIR" -maxdepth 1 -type f -name "${pkg}_${version}_*.deb" -print -quit)"
    [[ -n "$deb" ]] || { log "Cannot roll back: cached $pkg $version package is missing"; return 3; }
    debs+=("$deb")
  done < "$STATE_DIR/previous-packages"
  (( ${#debs[@]} > 0 )) || return 2
  log "Rolling back BRZ-owned packages from verified local cache"
  DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-downgrades --no-install-recommends "${debs[@]}"
  rm -f "$STATE_DIR/update-pending" "$STATE_DIR/boot-attempts"
  systemctl restart brz-health-agent.service || true
  log "Package rollback completed"
}

repair() {
  need_root; init_dirs
  log "Starting conservative local repair"
  if dpkg --audit | grep -q .; then
    dpkg --configure -a
    DEBIAN_FRONTEND=noninteractive apt-get -f install -y
  fi
  systemctl daemon-reload
  systemctl enable brz-health-agent.service
  systemctl restart brz-health-agent.service
  if ! health_check; then
    [[ -e "$STATE_DIR/update-pending" ]] && rollback_packages
  fi
}

boot_attempt() {
  need_root; init_dirs
  [[ -e "$STATE_DIR/update-pending" || -e "$STATE_DIR/os-update-pending" ]] || exit 0
  local attempts=0
  [[ -r "$STATE_DIR/boot-attempts" ]] && read -r attempts < "$STATE_DIR/boot-attempts"
  [[ "$attempts" =~ ^[0-9]+$ ]] || attempts=0
  attempts=$((attempts + 1))
  printf '%s\n' "$attempts" > "$STATE_DIR/boot-attempts"
  log "Post-update boot attempt $attempts of $MAX_BOOT_ATTEMPTS"
  if (( attempts >= MAX_BOOT_ATTEMPTS )); then
    if [[ -e "$STATE_DIR/os-update-pending" ]]; then rollback_os; else rollback_packages; fi
  fi
}

confirm_boot() {
  need_root; init_dirs
  [[ -e "$STATE_DIR/update-pending" || -e "$STATE_DIR/os-update-pending" ]] || exit 0
  if health_check; then
    rm -f "$STATE_DIR/update-pending" "$STATE_DIR/os-update-pending" "$STATE_DIR/boot-attempts" "$STATE_DIR/previous-packages"
    log "Boot confirmed healthy; update accepted"
  else
    log "Boot health confirmation failed; update remains pending"
    exit 1
  fi
}

status() {
  init_dirs
  echo "BRZ HealthOS recovery"
  echo "  update pending: $([[ -e "$STATE_DIR/update-pending" ]] && echo yes || echo no)"
  echo "  boot attempts: $(cat "$STATE_DIR/boot-attempts" 2>/dev/null || echo 0) / $MAX_BOOT_ATTEMPTS"
  echo "  cached packages: $(find "$CACHE_DIR" -maxdepth 1 -type f -name '*.deb' | wc -l)"
  echo "  OS update pending: $([[ -e "$STATE_DIR/os-update-pending" ]] && echo yes || echo no)"
  if command -v snapper >/dev/null 2>&1 && findmnt -n -o FSTYPE / | grep -qx btrfs; then
    echo "  filesystem snapshots: available (Btrfs + snapper)"
  else
    echo "  filesystem snapshots: unavailable; package rollback only"
  fi
  health_check
}

case "${1:-status}" in
  status) status ;;
  check) init_dirs; health_check ;;
  repair) repair ;;
  prepare-update) prepare_update ;;
  rollback) rollback_packages ;;
  boot-attempt) boot_attempt ;;
  confirm-boot) confirm_boot ;;
  apply-updates) apply_os_updates ;;
  rollback-os) rollback_os ;;
  release-upgrade) release_upgrade "${2:-}" ;;
  *) echo "Usage: brz-healthos-recovery {status|check|repair|prepare-update|rollback|boot-attempt|confirm-boot|apply-updates|rollback-os|release-upgrade VERSION}" >&2; exit 2 ;;
esac
