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

SELF="${0##*/}"

# Binaries (some resolved later too, if deps get installed)
CURL="$(command -v curl || true)"
SUDO="$(command -v sudo || true)"
APT_GET="$(command -v apt-get || true)"
SYSTEMCTL="$(command -v systemctl || true)"
GROUPADD="$(command -v groupadd || true)"
USERADD="$(command -v useradd || true)"
SED="$(command -v sed || true)"
SHA256SUM="$(command -v sha256sum || true)"
ID="$(command -v id || true)"
UNAME="$(command -v uname || true)"
MKTEMP="$(command -v mktemp || true)"

die() { echo -e "$*" >&2; exit 1; }

is_root() { [ "${EUID:-$("$ID" -u)}" -eq 0 ]; }

run() { "$@"; }

as_root() {
  if is_root; then
    run "$@"
  else
    [ -n "${SUDO:-}" ] || die "sudo is required (or run as root)"
    "$SUDO" "$@"
  fi
}

################################################################################
# Options helper (internal)
################################################################################
opt_get() {
  local name="$1"
  local value="$2"
  shift 2

  local flag="1"
  if [ "${name: -1}" = ":" ]; then
    flag=""
    name="${name%*:}"
  fi

  while [ "$#" -gt 0 ]; do
    case "$1" in
      -"$name"|--"$name")
        if [ -n "$flag" ]; then
          value="1"
        else
          value="${2:-}"
          shift
        fi
        ;;
      -"$name="*|--"$name="*)
        value="${1#*=}"
        ;;
    esac
    shift
  done
  echo "$value"
}

################################################################################
# Detect LXC/container vs VM/bare metal
################################################################################
is_lxc_container() {
  # Prefer systemd-detect-virt if available
  if command -v systemd-detect-virt >/dev/null 2>&1; then
    local v
    v="$(systemd-detect-virt --container 2>/dev/null || true)"
    [ "$v" = "lxc" ] && return 0
  fi

  # Fallback heuristics
  if [ -r /proc/1/environ ] && tr '\0' '\n' </proc/1/environ 2>/dev/null | grep -qi '^container=lxc$'; then
    return 0
  fi
  if [ -r /proc/1/cgroup ] && grep -qi 'lxc' /proc/1/cgroup; then
    return 0
  fi
  return 1
}

################################################################################
# Ensure deps: only run apt update/install if curl or sudo is missing
################################################################################
ensure_deps() {
  APT_GET="$(command -v apt-get || true)"
  [ -n "$APT_GET" ] || die "apt-get not found (this installer expects Debian-based OS)"

  local need_apt=0
  local pkgs=()

  if ! command -v curl >/dev/null 2>&1; then
    pkgs+=("curl")
    need_apt=1
  fi

  if ! command -v sudo >/dev/null 2>&1; then
    # Can't install sudo without root
    if ! is_root; then
      die "sudo is not installed. Run this script as root once to install sudo."
    fi
    pkgs+=("sudo")
    need_apt=1
  fi

  if [ "$need_apt" -eq 1 ]; then
    echo "Installing missing dependencies: ${pkgs[*]}"
    as_root env DEBIAN_FRONTEND=noninteractive "$APT_GET" update -y
    as_root env DEBIAN_FRONTEND=noninteractive "$APT_GET" install -y "${pkgs[@]}"
  fi

  # Re-resolve binaries after potential install
  CURL="$(command -v curl || true)"
  SUDO="$(command -v sudo || true)"
  SYSTEMCTL="$(command -v systemctl || true)"
  GROUPADD="$(command -v groupadd || true)"
  USERADD="$(command -v useradd || true)"
  SED="$(command -v sed || true)"
  SHA256SUM="$(command -v sha256sum || true)"
  ID="$(command -v id || true)"
  UNAME="$(command -v uname || true)"
  MKTEMP="$(command -v mktemp || true)"

  [ -n "$CURL" ] || die "curl not available"
  [ -n "$SUDO" ] || is_root || die "sudo not available"
}

################################################################################
# Preflight
################################################################################
preflight() {
  ensure_deps

  [ -n "$GROUPADD" ] || die "Failed to initialize installer: groupadd not found"
  [ -n "$USERADD" ] || die "Failed to initialize installer: useradd not found"
  [ -n "$SED" ] || die "Failed to initialize installer: sed not found"
  [ -n "$SHA256SUM" ] || die "Failed to initialize installer: sha256sum not found"
  [ -n "$SYSTEMCTL" ] || die "Failed to initialize installer: systemctl not found"

  # If not root, validate sudo works
  if ! is_root; then
    "$SUDO" -v || die "Failed to initialize installer: root privileges required"
  fi
}

################################################################################
# Account action
################################################################################
account() {
  as_root "$GROUPADD" -f -r monito || die "Failed to create account: can't create group"
  "$ID" -u monito >/dev/null 2>&1 && return 0
  as_root "$USERADD" -r -g monito -d /usr/lib/monito monito || die "Failed to create account: can't create user"
}

################################################################################
# Network params requirement
################################################################################
configure_ping_group_range() {
  if is_lxc_container; then
    # LXC container
    as_root bash -c 'echo "0 65535" > /proc/sys/net/ipv4/ping_group_range' \
      || die "Failed to install agent: can't change ping group range (LXC)"
  else
    # VM / Bare metal
    if [ -x /usr/sbin/sysctl ]; then
      as_root /usr/sbin/sysctl -w "net.ipv4.ping_group_range=0 429296729" >/dev/null \
        || die "Failed to install agent: can't change ping group range (VM/BM)"
    else
      as_root sysctl -w "net.ipv4.ping_group_range=0 429296729" >/dev/null \
        || die "Failed to install agent: can't change ping group range (VM/BM)"
    fi
  fi
}

################################################################################
# Download helper
################################################################################
download() {
  local status
  status="$("$CURL" -o "$2" -s -w "%{http_code}" "$1")"
  [ "$status" = "200" ]
}

platform() {
  [ "$("$UNAME" -s)" != "Linux" ] && return 0
  case "$("$UNAME" -m)" in
    aarch64) echo "linux/arm64" ;;
    x86_64) echo "linux/amd64" ;;
  esac
}

################################################################################
# Install action
################################################################################
install() {
  local mirror target token address version
  mirror="$(opt_get "mirror:" "${MONITO_MIRROR:-https://get.monito.run}" "$@")"
  target="$(opt_get "platform:" "${MONITO_PLATFORM:-$(platform)}" "$@")"
  token="$(opt_get "token:" "${MONITO_TOKEN:-}" "$@")"
  address="$(opt_get "address:" "${MONITO_URL:-api.monito.run:443}" "$@")"
  version="$(opt_get "version:" "latest" "$@")"

  if [ -z "$(opt_get "silent" "" "$@")" ]; then
    echo "This utility will walk you through monito agent installation."
    echo "It only covers the commons, and tries to guess sensible defaults."
    echo "Note that it removes any previous agent installation."
    echo
    echo "Press ^C at any time to quit."

    exec 3</dev/tty || exec 3<&0
    read -rp "address: " -ei "$address" address <&3
    read -rp "token: " -ei "$token" token <&3
  fi

  [ -n "$mirror" ] || die "Failed to install agent: mirror is required"
  [ -n "$target" ] || die "Failed to install agent: unsupported platform"
  [ -n "$version" ] || die "Failed to install agent: version is required"
  [ -n "$address" ] || die "Failed to install agent: address is required"
  [ -n "$token" ] || die "Failed to install agent: token is required"

  mirror="$(echo "$mirror" | "$SED" 's:/*$::')/$version"
  local suffix="${target//\//-}"

  local temp
  temp="$("$MKTEMP" -d)"

  download "$mirror/agent-$suffix" "$temp/agent-$suffix" || { rm -rf "$temp"; die "Failed to install agent: missing agent"; }
  download "$mirror/sha256.txt" "$temp/sha256.txt" || { rm -rf "$temp"; die "Failed to install agent: missing checksum"; }

  "$SED" -i -E "s|([ ]+)|\1$temp/|g" "$temp/sha256.txt"
  "$SHA256SUM" -c --status --ignore-missing "$temp/sha256.txt" || { rm -rf "$temp"; die "Failed to install agent: invalid checksum"; }

  local agent="$temp/agent-$suffix"
  as_root chmod +x "$agent"

  "$agent" config --address "$address" --storage /usr/lib/monito --token "$token" > "$temp/agent.yml" \
    || { rm -rf "$temp"; die "Failed to install agent: can't create config"; }

  configure_ping_group_range

  as_root mkdir -p "/etc/monito" "/usr/lib/monito"
  as_root chown -R monito:monito "/usr/lib/monito"

  as_root cp -f "$agent" "/usr/lib/monito/agent"
  as_root cp -f "$temp/agent.yml" "/etc/monito/agent.yml"

  # Allow agent (user monito) to modify config and perform atomic updates:
  # - needs write to file (edit)
  # - needs write to dir (create temp file + rename)
  as_root chown root:monito "/etc/monito"
  as_root chmod 0770 "/etc/monito"

  as_root chown monito:monito "/etc/monito/agent.yml"
  as_root chmod 0660 "/etc/monito/agent.yml"

  if [ ! -e "/usr/local/bin/monito-agent" ]; then
    as_root ln -s "/usr/lib/monito/agent" "/usr/local/bin/monito-agent" >/dev/null 2>&1 || true
  fi

  rm -rf "$temp"
}

################################################################################
# Service action
################################################################################
service() {
  as_root bash -c 'cat > /etc/systemd/system/monito-agent.service <<EOF
[Unit]
Description=Monito Agent
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/monito-agent service --config /etc/monito/agent.yml /usr/lib/monito/monito-agent.pid
ExecReload=/bin/kill -HUP \$MAINPID
KillMode=process
TimeoutStopSec=20
PIDFile=/usr/lib/monito/monito-agent.pid
User=monito
Group=monito
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF'

  as_root "$SYSTEMCTL" daemon-reload
  as_root "$SYSTEMCTL" enable monito-agent >/dev/null
  as_root "$SYSTEMCTL" restart monito-agent >/dev/null
}

################################################################################
# Usage
################################################################################
usage() {
  echo "Usage: $SELF [OPTIONS]"
  echo
  echo 'Install monito agent.'
  echo
  echo 'Options:'
  echo '  --address ADDRESS       address of the monito api'
  echo '  --mirror URL            address of the repository'
  echo '  --platform PLATFORM     platform of the agent'
  echo '  --silent                suppress user prompts'
  echo '  --token TOKEN           agent token'
  echo '  --version VERSION       version of the agent to install'
}

################################################################################
# Main
################################################################################
if [ -n "$(opt_get "h" "" "$@")$(opt_get "help" "" "$@")" ]; then
  usage
  exit 0
fi

preflight
account
install "$@"
service
