#!/usr/bin/env bash

set -euo pipefail

# Nilo Linux Runner installer
# Usage: curl -sfL https://downloads.nilo-assistant.com/nilo-runner/linux/latest/install.sh | sudo bash

BINARY_NAME="nilo-runner-linux"
INSTALL_DIR="/usr/local/bin"
DEFAULT_REMOTE_URL="https://app.nilo-assistant.com"
DOWNLOAD_ROOT_URL="https://downloads.nilo-assistant.com/nilo-runner/linux"

log_info() {
  echo "[INFO] $*"
}

log_error() {
  echo "[ERROR] $*" >&2
}

detect_arch() {
  local arch
  arch=$(uname -m)
  case "$arch" in
    x86_64)
      echo "amd64"
      ;;
    aarch64|arm64)
      echo "arm64"
      ;;
    *)
      log_error "Unsupported architecture: $arch"
      exit 1
      ;;
  esac
}

download_binary() {
  local url="$1"
  local output="$2"

  log_info "Downloading binary from $url"

  if command -v curl >/dev/null 2>&1; then
    curl -fsSL --retry 3 -o "$output" "$url"
  elif command -v wget >/dev/null 2>&1; then
    wget -q --tries=3 -O "$output" "$url"
  else
    log_error "Neither curl nor wget is available"
    exit 1
  fi
}

download_verified_binary() {
  local arch="$1"
  local output="$2"
  local artifact="${BINARY_NAME}-${arch}"
  local metadata_directory
  metadata_directory=$(dirname "$output")
  download_binary "$DOWNLOAD_ROOT_URL/latest/release.txt" "$metadata_directory/release.txt" || return 1
  RELEASE_VERSION=$(cat "$metadata_directory/release.txt")
  if [[ ! "$RELEASE_VERSION" =~ ^[0-9a-f]{40}$ ]]; then
    log_error "The published release reference is invalid"
    return 1
  fi
  local release_url="$DOWNLOAD_ROOT_URL/releases/$RELEASE_VERSION"
  download_binary "$release_url/SHA256SUMS" "$metadata_directory/SHA256SUMS" || return 1
  download_verified_artifact "$artifact" "$output" || return 1
  chmod +x "$output"
  if [[ "$("$output" --version)" != "$RELEASE_VERSION" ]]; then
    log_error "The downloaded binary does not report its published source version"
    return 1
  fi
  log_info "Verified Runner release: $RELEASE_VERSION"
}

# All artifacts are selected from the single immutable release read above.
download_verified_artifact() {
  local artifact="$1" output="$2" expected actual
  expected=$(awk -v artifact="$artifact" '$2 == artifact { print $1 }' "$(dirname "$output")/SHA256SUMS")
  if [[ ! "$expected" =~ ^[0-9a-f]{64}$ ]]; then
    log_error "The release must contain exactly one checksum for $artifact"
    return 1
  fi
  download_binary "$DOWNLOAD_ROOT_URL/releases/$RELEASE_VERSION/$artifact" "$output" || return 1
  actual=$(sha256sum "$output" | awk '{print $1}')
  if [[ "$expected" != "$actual" ]]; then
    log_error "The downloaded artifact does not match its release checksum: $artifact"
    return 1
  fi
}

# A socket grants this account only the fixed, verified release-install operation.
install_updater() {
  install -d -m 0755 /usr/local/libexec
  install -m 0755 "$1" /usr/local/libexec/nilo-runner-updater
  install -d -o root -g "$RUNNER_GROUP" -m 0750 /var/lib/nilo-runner-update
  cat > /etc/systemd/system/nilo-runner-update.socket <<EOF
[Unit]
Description=Nilo Runner verified update socket

[Socket]
ListenStream=/run/nilo-runner-update.sock
SocketUser=${RUNNER_USER}
SocketGroup=${RUNNER_GROUP}
SocketMode=0600
Accept=yes
MaxConnections=1
RemoveOnStop=yes

[Install]
WantedBy=sockets.target
EOF
  cat > /etc/systemd/system/nilo-runner-update@.service <<EOF
[Unit]
Description=Nilo Runner verified release installation

[Service]
User=root
Group=${RUNNER_GROUP}
ExecStart=/usr/local/libexec/nilo-runner-updater
StandardInput=socket
StandardOutput=socket
StandardError=journal
RuntimeMaxSec=240
MemoryMax=256M
TasksMax=32
RuntimeDirectory=nilo-runner-update
RuntimeDirectoryMode=0750
StateDirectory=nilo-runner-update
StateDirectoryMode=0750
UMask=0027
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
NoNewPrivileges=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ReadWritePaths=/usr/local/bin /usr/local/libexec /var/lib/nilo-runner-update /run/nilo-runner-update
EOF
  chmod 0644 /etc/systemd/system/nilo-runner-update.socket /etc/systemd/system/nilo-runner-update@.service
}

install_binary() {
  local staged
  staged=$(mktemp "$INSTALL_DIR/.$BINARY_NAME.XXXXXX")
  if ! install -m 0755 "$1" "$staged"; then
    rm -f "$staged"
    return 1
  fi
  if ! mv -f "$staged" "$INSTALL_DIR/$BINARY_NAME"; then
    rm -f "$staged"
    return 1
  fi
}

prompt_required() {
  local prompt_text="$1"
  local value=""
  while [[ -z "$value" ]]; do
    if ! IFS= read -r -p "$prompt_text" value </dev/tty; then
      log_error "The installer needs an interactive terminal to read setup values."
      exit 1
    fi
    value=$(echo "$value" | xargs)
    if [[ -z "$value" ]]; then
      echo "This field is required. Please try again." >&2
    fi
  done
  echo "$value"
}

prompt_optional() {
  local prompt_text="$1"
  local default_value="${2:-}"
  local value
  if ! IFS= read -r -p "$prompt_text" value </dev/tty; then
    log_error "The installer needs an interactive terminal to read setup values."
    exit 1
  fi
  value=$(echo "$value" | xargs)
  if [[ -z "$value" && -n "$default_value" ]]; then
    echo "$default_value"
  else
    echo "$value"
  fi
}

# Managed bootstrap supplies credentials through the process environment, never CLI arguments or logs.
read_installation_settings() {
  if [[ "${NILO_RUNNER_NON_INTERACTIVE:-0}" == 1 ]]; then
    REMOTE_URL="${NILO_RUNNER_REMOTE_URL:-}"
    REGISTRATION_KEY="${NILO_RUNNER_REGISTRATION_KEY:-}"
    DEVICE_NAME="${NILO_RUNNER_DEVICE_NAME:-}"
    RUNNER_USER="${NILO_RUNNER_USER:-}"
    if [[ -z "$REMOTE_URL" || -z "$REGISTRATION_KEY" || -z "$DEVICE_NAME" || -z "$RUNNER_USER" ]]; then
      log_error "Managed installation requires remote URL, registration key, device name and Linux account."
      return 1
    fi
  elif [[ "${NILO_RUNNER_NON_INTERACTIVE:-0}" == 0 ]]; then
    REMOTE_URL=$(prompt_optional "Remote Nilo base URL [$DEFAULT_REMOTE_URL]: " "$DEFAULT_REMOTE_URL")
    REGISTRATION_KEY=$(prompt_required "Runner registration key: ")
    DEVICE_NAME=$(prompt_optional "Device name [$(hostname)]: " "$(hostname)")
    RUNNER_USER="${NILO_RUNNER_USER:-${SUDO_USER:-}}"
    if [[ -z "$RUNNER_USER" ]]; then
      RUNNER_USER=$(prompt_required "Linux account that should run this runner: ")
    fi
  else
    log_error "NILO_RUNNER_NON_INTERACTIVE must be 0 or 1."
    return 1
  fi
  for value in "$REMOTE_URL" "$REGISTRATION_KEY" "$DEVICE_NAME" "$RUNNER_USER"; do
    if [[ "$value" == *$'\n'* || "$value" == *$'\r'* ]]; then
      log_error "Installation settings must be single-line values."
      return 1
    fi
  done
}

# Escape systemd quoted values without changing the registered credential or device name.
systemd_environment_value() {
  local value="$1"
  value="${value//\\/\\\\}"
  value="${value//\"/\\\"}"
  value="${value//%/%%}"
  printf '%s' "$value"
}

main() {

  if [[ "$EUID" -ne 0 ]]; then
    log_error "This script must be run as root (use sudo)"
    exit 1
  fi

  echo ""
  echo "=== Nilo Linux Runner Installation ==="
  echo ""

  read_installation_settings
  if ! id -u "$RUNNER_USER" >/dev/null 2>&1; then
    log_error "Linux account does not exist: $RUNNER_USER"
    exit 1
  fi
  RUNNER_GROUP=$(id -gn "$RUNNER_USER")
  RUNNER_HOME=$(getent passwd "$RUNNER_USER" | awk -F: '{print $6}')
  RUNNER_SHELL=$(getent passwd "$RUNNER_USER" | awk -F: '{print $7}')
  if [[ -z "$RUNNER_HOME" || -z "$RUNNER_SHELL" || ! -x "$RUNNER_SHELL" ]]; then
    log_error "Linux account must have an executable login shell: $RUNNER_USER"
    exit 1
  fi
  DATA_DIR="$RUNNER_HOME/.local/share/nilo-runner"

  echo ""
  log_info "Remote URL: $REMOTE_URL"
  log_info "Device name: $DEVICE_NAME"
  log_info "Runner account: $RUNNER_USER"

  ARCH=$(detect_arch)
  log_info "Detected architecture: $ARCH"

  TMP_DIR=$(mktemp -d)
  trap 'rm -rf "$TMP_DIR"' EXIT

  BINARY_PATH="$TMP_DIR/$BINARY_NAME"
  download_verified_binary "$ARCH" "$BINARY_PATH"
  download_verified_artifact updater.sh "$TMP_DIR/updater.sh"
  bash -n "$TMP_DIR/updater.sh"

  # Verify binary works
  if ! "$BINARY_PATH" --help >/dev/null 2>&1; then
    log_error "Downloaded binary does not appear to be valid"
    exit 1
  fi

  log_info "Installing Nilo Linux Runner..."

  # Replace the executable atomically, including when the prior version is running.
  install_updater "$TMP_DIR/updater.sh"
  install_binary "$BINARY_PATH"

  # Keep runner state in the same account that owns its commands and files.
  mkdir -p "$DATA_DIR"
  chown "$RUNNER_USER:$RUNNER_GROUP" "$DATA_DIR"
  chmod 700 "$DATA_DIR"

  # Preserve the existing device identity when upgrading from the former system-user install.
  if [[ -f /var/lib/nilo-runner/device-id && ! -f "$DATA_DIR/device-id" ]]; then
    cp /var/lib/nilo-runner/device-id "$DATA_DIR/device-id"
    chown "$RUNNER_USER:$RUNNER_GROUP" "$DATA_DIR/device-id"
    chmod 600 "$DATA_DIR/device-id"
  fi
  if [[ -f /etc/sudoers.d/nilo-runner ]]; then
    rm /etc/sudoers.d/nilo-runner
    log_info "Removed legacy elevated permissions for the former nilo service account"
  fi

  # Install systemd service
  SERVICE_FILE="/etc/systemd/system/nilo-runner.service"
  cat > "$SERVICE_FILE" <<EOF
[Unit]
Description=Nilo Linux Runner
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=${RUNNER_USER}
Group=${RUNNER_GROUP}
ExecStart=${INSTALL_DIR}/${BINARY_NAME}
Restart=always
RestartSec=10
Environment="NILO_RUNNER_REMOTE_URL=$(systemd_environment_value "$REMOTE_URL")"
Environment="NILO_RUNNER_REGISTRATION_KEY=$(systemd_environment_value "$REGISTRATION_KEY")"
Environment="NILO_RUNNER_DEVICE_NAME=$(systemd_environment_value "$DEVICE_NAME")"
Environment="NILO_RUNNER_SHELL=${RUNNER_SHELL}"
Environment="NILO_RUNNER_DATA_DIR=${DATA_DIR}"
Environment="NILO_RUNNER_LOG_LEVEL=info"
Environment="HOME=${RUNNER_HOME}"
Environment="SHELL=${RUNNER_SHELL}"
WorkingDirectory=${RUNNER_HOME}
ReadWritePaths=${DATA_DIR}
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

  chmod 644 "$SERVICE_FILE"

  # Restart also activates the new executable for an already enabled service.
  systemctl daemon-reload
  systemctl enable --now nilo-runner-update.socket
  systemctl enable nilo-runner.service
  systemctl restart nilo-runner.service

  echo ""
  log_info "Installation complete."
  echo ""
  echo "The runner service is enabled and running."
  echo ""
  echo "Check status with:"
  echo "  sudo systemctl status nilo-runner"
  echo ""
  echo "View logs with:"
  echo "  sudo journalctl -u nilo-runner -f"
}

if [[ "${BASH_SOURCE[0]:-$0}" == "$0" ]]; then
  main "$@"
fi
