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

log() {
  printf '%s\n' "==> $*"
}

die() {
  printf '%s\n' "Error: $*" >&2
  exit 1
}

have_command() {
  command -v "$1" >/dev/null 2>&1
}

as_root() {
  if [ "$(id -u)" -eq 0 ]; then
    "$@"
  elif have_command sudo; then
    sudo "$@"
  else
    die "this installer needs root privileges; re-run as root or install sudo"
  fi
}

# Capability-first host support (PROJECT.md "Linux support strategy"): GameRail runs
# on any systemd Linux host on a supported CPU architecture, regardless of the exact
# distro or version. Dependencies are installed through whichever native package
# manager is present; unknown systems get clear manual remediation instead of guesses.
require_supported_host() {
  local arch os_id os_name systemd_dir os_release_file
  arch="${GAMERAIL_ARCH:-$(uname -m)}"
  [ "$arch" = "x86_64" ] || die "unsupported CPU architecture '$arch'; GameRail currently ships an x86_64 Linux binary"

  os_release_file="${GAMERAIL_OS_RELEASE_FILE:-/etc/os-release}"
  if [ -r "$os_release_file" ]; then
    # shellcheck disable=SC1091
    . "$os_release_file"
    os_id="${ID:-linux}"
    os_name="${PRETTY_NAME:-$os_id ${VERSION_ID:-}}"
  else
    os_name="unknown Linux"
  fi
  log "Detected host: $os_name ($arch)"

  have_command systemctl || die "systemctl is required; GameRail currently supports systemd hosts"
  systemd_dir="${GAMERAIL_SYSTEMD_DIR:-/etc/systemd/system}"
  [ -d "$systemd_dir" ] || die "systemd unit directory '$systemd_dir' does not exist"
}

# Detect the native package manager and echo its name, or nothing if unknown.
detect_package_manager() {
  local pm
  for pm in apt-get dnf yum pacman apk zypper; do
    if have_command "$pm"; then
      printf '%s\n' "$pm"
      return 0
    fi
  done
  return 1
}

install_dependencies() {
  if [ "${GAMERAIL_SKIP_PACKAGES:-false}" = "true" ]; then
    log "Skipping package installation"
    return
  fi

  local pm
  if ! pm="$(detect_package_manager)"; then
    die "no supported package manager found (apt, dnf/yum, pacman, apk, zypper). Install these packages manually, then re-run with GAMERAIL_SKIP_PACKAGES=true: ca-certificates curl iproute2"
  fi

  log "Installing GameRail base dependencies with $pm"
  # Package names are identical across these distros except iproute2, which Fedora/RHEL
  # call "iproute".
  case "$pm" in
    apt-get)
      as_root apt-get update
      as_root apt-get install -y ca-certificates curl iproute2
      ;;
    dnf)
      as_root dnf install -y ca-certificates curl iproute
      ;;
    yum)
      as_root yum install -y ca-certificates curl iproute
      ;;
    pacman)
      as_root pacman -Sy --needed --noconfirm ca-certificates curl iproute2
      ;;
    apk)
      as_root apk add --no-cache ca-certificates curl iproute2
      ;;
    zypper)
      as_root zypper --non-interactive install ca-certificates curl iproute2
      ;;
  esac

  log "Module-specific dependencies are installed later from the dashboard when a module is enabled."
}

# Default is the Railway-hosted relay; switch to relay.gamerail.app once its DNS exists,
# either by setting GAMERAIL_RELAY_BASE or by updating this default.
RELAY_BASE="${GAMERAIL_RELAY_BASE:-https://relay-production-208f.up.railway.app}"
DASHBOARD_BASE="${GAMERAIL_DASHBOARD_BASE:-https://dashboard.gamerail.app}"

download_binary() {
  local output url
  output="$1"
  url="${GAMERAIL_BINARY_URL:-}"
  if [ -z "$url" ]; then
    url="${GAMERAIL_RELEASE_BASE_URL:-https://github.com/Logan9312/GameRail-releases/releases/latest/download}/gamerail-agent-linux-x86_64"
  fi

  log "Downloading GameRail agent from $url"
  have_command curl || die "curl is required"
  curl -fsSL "$url" -o "$output" || die "failed to download GameRail agent from $url"
  chmod 0755 "$output"
}

write_env_if_missing() {
  local path content tmp
  path="$1"
  content="$2"

  if [ -f "$path" ]; then
    log "Keeping existing $path"
    return
  fi

  tmp="$(mktemp)"
  printf '%s\n' "$content" >"$tmp"
  as_root install -m 0600 "$tmp" "$path"
  rm -f "$tmp"
  log "Created $path"
}

existing_env_value() {
  local path key
  path="$1"
  key="$2"
  [ -e "$path" ] || return 0
  as_root sed -n "s/^${key}=//p" "$path" | tail -n 1 | sed 's/^"//; s/"$//'
}

print_dashboard_connect() {
  local controller_id claim_token
  controller_id="$1"
  claim_token="$2"
  [ -n "$controller_id" ] && [ -n "$claim_token" ] || return 0
  printf '\n%s\n' "Open this link within 15 minutes to attach the controller to your account:"
  printf '  %s\n' "$DASHBOARD_BASE/#controller=$controller_id&claim=$claim_token"
  printf '\n%s\n' "The claim is single-use. Sign in when prompted."
}

dashboard_public_key() {
  if [ -n "${GAMERAIL_DASHBOARD_PUBLIC_KEY:-}" ]; then
    printf '%s\n' "$GAMERAIL_DASHBOARD_PUBLIC_KEY"
    return
  fi
  curl -fsSL "$DASHBOARD_BASE/api/auth/public-key" |
    sed -n 's/.*"publicKey"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p'
}

relay_ws_base() {
  case "$RELAY_BASE" in
    https://*) printf '%s\n' "wss${RELAY_BASE#https}" ;;
    http://*) printf '%s\n' "ws${RELAY_BASE#http}" ;;
    *) printf '%s\n' "$RELAY_BASE" ;;
  esac
}

random_token() {
  if have_command openssl; then
    openssl rand -hex 32
  else
    od -An -N32 -tx1 /dev/urandom | tr -d ' \n'
  fi
}

write_env() {
  local path tmp
  path="$1"
  tmp="$(mktemp)"
  cat >"$tmp"
  as_root install -m 0600 "$tmp" "$path"
  rm -f "$tmp"
}

write_env_without_controller_relay_keys() {
  local path tmp
  path="$1"
  tmp="$(mktemp)"
  as_root sed \
    -e '/^GAMERAIL_RELAY_URL=/d' \
    -e '/^GAMERAIL_RELAY_CONTROLLER_ID=/d' \
    -e '/^GAMERAIL_CONTROLLER_AUTH_STATE=/d' \
    -e '/^GAMERAIL_DASHBOARD_PUBLIC_KEY=/d' \
    "$path" >"$tmp"
  as_root install -m 0600 "$tmp" "$path"
  rm -f "$tmp"
}

refresh_controller_relay_env() {
  local controller_env auth_state public_key relay_base_ws
  controller_env="$1"
  auth_state="$2"
  public_key="$3"
  relay_base_ws="$(relay_ws_base)"

  write_env_without_controller_relay_keys "$controller_env"
  printf 'GAMERAIL_RELAY_URL=%s\nGAMERAIL_CONTROLLER_AUTH_STATE=%s\nGAMERAIL_DASHBOARD_PUBLIC_KEY=%s\n' \
    "$relay_base_ws" "$auth_state" "$public_key" | as_root tee -a "$controller_env" >/dev/null ||
    die "failed to append relay config to $controller_env"
}

write_initial_configs() {
  local config_dir controller_env agent_env
  config_dir="$1"
  controller_env="$config_dir/controller.env"
  agent_env="$config_dir/agent.env"

  write_env_if_missing "$controller_env" "GAMERAIL_CONTROLLER_ADDR=${GAMERAIL_CONTROLLER_ADDR:-127.0.0.1:8787}
GAMERAIL_CONTROLLER_CONFIG=${GAMERAIL_CONTROLLER_CONFIG:-$config_dir/fleet.toml}
GAMERAIL_CONTROLLER_ENDPOINT=${GAMERAIL_CONTROLLER_ENDPOINT:-}
GAMERAIL_API_TOKEN=
GAMERAIL_CONTROLLER_AUTH_STATE=${GAMERAIL_CONTROLLER_AUTH_STATE:-$config_dir/controller-auth.json}
GAMERAIL_DASHBOARD_PUBLIC_KEY=${GAMERAIL_DASHBOARD_PUBLIC_KEY:-}"

  write_env_if_missing "$agent_env" "GAMERAIL_CONTROLLER_ENDPOINT=${GAMERAIL_AGENT_CONTROLLER_ENDPOINT:-${GAMERAIL_CONTROLLER_ENDPOINT:-}}
GAMERAIL_NODE_ID=${GAMERAIL_NODE_ID:-}
GAMERAIL_NODE_TOKEN=${GAMERAIL_NODE_TOKEN:-}
GAMERAIL_NODE_NAME=${GAMERAIL_NODE_NAME:-}
GAMERAIL_APPLY_SYSTEM=${GAMERAIL_APPLY_SYSTEM:-false}"
}

write_configure_command() {
  local install_dir configure_path tmp
  install_dir="$1"
  configure_path="$install_dir/gamerail-configure"
  tmp="$(mktemp)"
  cat >"$tmp" <<'CONFIGURE_SCRIPT'
#!/usr/bin/env bash
set -euo pipefail

CONFIG_DIR="${GAMERAIL_CONFIG_DIR:-/etc/gamerail}"
CONTROLLER_ENV="${GAMERAIL_CONTROLLER_ENV_FILE:-$CONFIG_DIR/controller.env}"
AGENT_ENV="${GAMERAIL_AGENT_ENV_FILE:-$CONFIG_DIR/agent.env}"
CONTROLLER_SERVICE="${GAMERAIL_CONTROLLER_SERVICE_NAME:-gamerail-controller.service}"
AGENT_SERVICE="${GAMERAIL_AGENT_SERVICE_NAME:-gamerail-agent.service}"

die() {
  printf '%s\n' "Error: $*" >&2
  exit 1
}

usage() {
  cat <<'USAGE'
Usage:
  gamerail-configure controller [--addr 127.0.0.1:8787] [--config /etc/gamerail/fleet.toml] [--endpoint URL]
  gamerail-configure agent --controller URL --node-id ID --token TOKEN [--name NAME] [--apply-system]

Controller and agent configuration are independent. Configuring one service does not disable the other.
USAGE
}

existing_env_value() {
  local path key
  path="$1"
  key="$2"
  [ -r "$path" ] || return 0
  sed -n "s/^${key}=//p" "$path" | tail -n 1 | sed 's/^"//; s/"$//'
}

random_token() {
  if command -v openssl >/dev/null 2>&1; then
    openssl rand -hex 32
  else
    od -An -N32 -tx1 /dev/urandom | tr -d ' \n'
  fi
}

need_value() {
  local flag value
  flag="$1"
  value="${2:-}"
  [ -n "$value" ] || die "$flag requires a value"
  printf '%s' "$value"
}

quote_env() {
  local value
  value="$1"
  value="${value//\\/\\\\}"
  value="${value//\"/\\\"}"
  printf '"%s"' "$value"
}

install_env() {
  local path tmp
  path="$1"
  tmp="$(mktemp)"
  cat >"$tmp"
  if [ "$(id -u)" -eq 0 ]; then
    mkdir -p "$CONFIG_DIR"
    install -m 0600 "$tmp" "$path"
  elif command -v sudo >/dev/null 2>&1; then
    sudo mkdir -p "$CONFIG_DIR"
    sudo install -m 0600 "$tmp" "$path"
  else
    rm -f "$tmp"
    die "root privileges are required to write $path"
  fi
  rm -f "$tmp"
}

systemctl_maybe() {
  if ! command -v systemctl >/dev/null 2>&1; then
    return
  fi
  if [ "$(id -u)" -eq 0 ]; then
    systemctl "$@"
  elif command -v sudo >/dev/null 2>&1; then
    sudo systemctl "$@"
  fi
}

configure_controller() {
  local addr config endpoint api_token
  addr="127.0.0.1:8787"
  config="$CONFIG_DIR/fleet.toml"
  endpoint=""
  api_token="$(existing_env_value "$CONTROLLER_ENV" GAMERAIL_API_TOKEN)"

  while [ "$#" -gt 0 ]; do
    case "$1" in
      --addr) addr="$(need_value "$1" "${2:-}")"; shift 2 ;;
      --config) config="$(need_value "$1" "${2:-}")"; shift 2 ;;
      --endpoint) endpoint="$(need_value "$1" "${2:-}")"; shift 2 ;;
      -h|--help) usage; exit 0 ;;
      *) die "unknown controller option: $1" ;;
    esac
  done
  [ -n "$api_token" ] || api_token="$(random_token)"

  {
    printf 'GAMERAIL_CONTROLLER_ADDR=%s\n' "$(quote_env "$addr")"
    printf 'GAMERAIL_CONTROLLER_CONFIG=%s\n' "$(quote_env "$config")"
    printf 'GAMERAIL_CONTROLLER_ENDPOINT=%s\n' "$(quote_env "$endpoint")"
    printf 'GAMERAIL_API_TOKEN=%s\n' "$(quote_env "$api_token")"
  } | install_env "$CONTROLLER_ENV"

  systemctl_maybe enable "$CONTROLLER_SERVICE"
  systemctl_maybe restart "$CONTROLLER_SERVICE"
  printf '%s\n' "Wrote $CONTROLLER_ENV and started $CONTROLLER_SERVICE"
  printf '%s\n' "Controller service secret is managed in $CONTROLLER_ENV"
}

configure_agent() {
  local endpoint node_id token node_name apply_system
  endpoint=""
  node_id=""
  token=""
  node_name=""
  apply_system="false"

  while [ "$#" -gt 0 ]; do
    case "$1" in
      --controller) endpoint="$(need_value "$1" "${2:-}")"; shift 2 ;;
      --node-id) node_id="$(need_value "$1" "${2:-}")"; shift 2 ;;
      --token) token="$(need_value "$1" "${2:-}")"; shift 2 ;;
      --name) node_name="$(need_value "$1" "${2:-}")"; shift 2 ;;
      --apply-system) apply_system="true"; shift ;;
      -h|--help) usage; exit 0 ;;
      *) die "unknown agent option: $1" ;;
    esac
  done

  [ -n "$endpoint" ] || die "agent mode requires --controller"
  [ -n "$node_id" ] || die "agent mode requires --node-id"
  [ -n "$token" ] || die "agent mode requires --token"

  {
    printf 'GAMERAIL_CONTROLLER_ENDPOINT=%s\n' "$(quote_env "$endpoint")"
    printf 'GAMERAIL_NODE_ID=%s\n' "$(quote_env "$node_id")"
    printf 'GAMERAIL_NODE_TOKEN=%s\n' "$(quote_env "$token")"
    printf 'GAMERAIL_NODE_NAME=%s\n' "$(quote_env "$node_name")"
    printf 'GAMERAIL_APPLY_SYSTEM=%s\n' "$(quote_env "$apply_system")"
  } | install_env "$AGENT_ENV"

  systemctl_maybe enable "$AGENT_SERVICE"
  systemctl_maybe restart "$AGENT_SERVICE"
  printf '%s\n' "Wrote $AGENT_ENV and started $AGENT_SERVICE"
}

[ "${1:-}" != "" ] || { usage; exit 2; }

mode="$1"
shift
case "$mode" in
  controller) configure_controller "$@" ;;
  agent) configure_agent "$@" ;;
  -h|--help) usage ;;
  *) die "mode must be 'controller' or 'agent'" ;;
esac
CONFIGURE_SCRIPT
  chmod 0755 "$tmp"
  as_root install -m 0755 "$tmp" "$configure_path"
  rm -f "$tmp"
}

write_controller_unit() {
  local install_dir config_dir unit_path tmp
  install_dir="$1"
  config_dir="$2"
  unit_path="${GAMERAIL_SYSTEMD_DIR:-/etc/systemd/system}/gamerail-controller.service"
  tmp="$(mktemp)"
  cat >"$tmp" <<UNIT
[Unit]
Description=GameRail local controller
Documentation=https://gamerail.app
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
EnvironmentFile=$config_dir/controller.env
ExecStart=/bin/sh -eu -c ': "\${GAMERAIL_API_TOKEN:?GAMERAIL_API_TOKEN is required}"; exec "$install_dir/gamerail-agent" controller --addr "\${GAMERAIL_CONTROLLER_ADDR:-127.0.0.1:8787}" --config "\${GAMERAIL_CONTROLLER_CONFIG:-$config_dir/fleet.toml}"'
Restart=on-failure
RestartSec=5s
StateDirectory=gamerail
RuntimeDirectory=gamerail
WorkingDirectory=$config_dir

[Install]
WantedBy=multi-user.target
UNIT
  as_root install -m 0644 "$tmp" "$unit_path"
  rm -f "$tmp"
}

write_agent_unit() {
  local install_dir config_dir unit_path tmp
  install_dir="$1"
  config_dir="$2"
  unit_path="${GAMERAIL_SYSTEMD_DIR:-/etc/systemd/system}/gamerail-agent.service"
  tmp="$(mktemp)"
  cat >"$tmp" <<UNIT
[Unit]
Description=GameRail node agent
Documentation=https://gamerail.app
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
EnvironmentFile=$config_dir/agent.env
ExecStart=/bin/sh -eu -c ': "\${GAMERAIL_NODE_TOKEN:?GAMERAIL_NODE_TOKEN is required}"; set -- "$install_dir/gamerail-agent" agent --controller "\${GAMERAIL_CONTROLLER_ENDPOINT:?GAMERAIL_CONTROLLER_ENDPOINT is required}" --node-id "\${GAMERAIL_NODE_ID:?GAMERAIL_NODE_ID is required}"; if [ -n "\${GAMERAIL_NODE_NAME:-}" ]; then set -- "\$@" --name "\$GAMERAIL_NODE_NAME"; fi; if [ "\${GAMERAIL_APPLY_SYSTEM:-false}" = "true" ]; then set -- "\$@" --apply-system; fi; exec "\$@"'
Restart=on-failure
RestartSec=5s
StateDirectory=gamerail
RuntimeDirectory=gamerail
WorkingDirectory=$config_dir

[Install]
WantedBy=multi-user.target
UNIT
  as_root install -m 0644 "$tmp" "$unit_path"
  rm -f "$tmp"
}

write_update_command() {
  local install_dir update_path tmp
  install_dir="$1"
  update_path="$install_dir/gamerail-update"
  tmp="$(mktemp)"
  cat >"$tmp" <<'UPDATE_SCRIPT'
#!/bin/sh
# Self-update: download the latest released agent, verify its checksum, and
# install it only when it differs from the running binary. Run by
# gamerail-update.timer so machines track releases without SSH.
set -eu
BASE="${GAMERAIL_RELEASE_BASE_URL:-https://github.com/Logan9312/GameRail-releases/releases/latest/download}"
BIN_NAME="gamerail-agent-linux-x86_64"
TARGET="${GAMERAIL_INSTALL_DIR:-/usr/local/bin}/gamerail-agent"
tmp="$(mktemp)"
sums="$(mktemp)"
staged="$(dirname "$TARGET")/.gamerail-agent.new.$$"
trap 'rm -f "$tmp" "$sums" "$staged"' EXIT
curl -fsSL "$BASE/$BIN_NAME" -o "$tmp"
curl -fsSL "$BASE/SHA256SUMS" -o "$sums"
expected="$(awk -v name="$BIN_NAME" '$2 == name || $2 == "*" name { print $1 }' "$sums")"
[ -n "$expected" ] || { echo "no checksum for $BIN_NAME in SHA256SUMS" >&2; exit 1; }
actual="$(sha256sum "$tmp" | awk '{ print $1 }')"
[ "$expected" = "$actual" ] || { echo "checksum mismatch for downloaded $BIN_NAME" >&2; exit 1; }
current="$(sha256sum "$TARGET" 2>/dev/null | awk '{ print $1 }' || true)"
if [ "$actual" = "$current" ]; then
  echo "GameRail is already up to date"
  exit 0
fi
install -m 0755 "$tmp" "$staged"
mv -f "$staged" "$TARGET"
systemctl try-restart gamerail-controller.service gamerail-agent.service || true
echo "GameRail updated to the latest release"
UPDATE_SCRIPT
  chmod 0755 "$tmp"
  as_root install -m 0755 "$tmp" "$update_path"
  rm -f "$tmp"
}

write_update_units() {
  local install_dir systemd_dir tmp
  install_dir="$1"
  systemd_dir="${GAMERAIL_SYSTEMD_DIR:-/etc/systemd/system}"

  tmp="$(mktemp)"
  cat >"$tmp" <<UNIT
[Unit]
Description=GameRail self-update
Documentation=https://gamerail.app
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=$install_dir/gamerail-update
UNIT
  as_root install -m 0644 "$tmp" "$systemd_dir/gamerail-update.service"
  rm -f "$tmp"

  tmp="$(mktemp)"
  cat >"$tmp" <<'UNIT'
[Unit]
Description=Daily GameRail self-update

[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target
UNIT
  as_root install -m 0644 "$tmp" "$systemd_dir/gamerail-update.timer"
  rm -f "$tmp"
}

install_units() {
  local install_dir config_dir
  install_dir="$1"
  config_dir="$2"
  write_controller_unit "$install_dir" "$config_dir"
  write_agent_unit "$install_dir" "$config_dir"
  write_update_command "$install_dir"
  write_update_units "$install_dir"
  as_root systemctl daemon-reload
  as_root systemctl enable --now gamerail-update.timer ||
    log "Could not enable the self-update timer; updates require re-running the installer"
}

bootstrap_first_machine() {
  local bin_path config_dir controller_env agent_env
  local agent_endpoint node_token api_token controller_addr controller_config
  local node_id node_name relay_controller_id auth_state auth_output claim_token public_key
  bin_path="$1"
  config_dir="$2"
  controller_env="$config_dir/controller.env"
  agent_env="$config_dir/agent.env"

  agent_endpoint="$(existing_env_value "$agent_env" GAMERAIL_CONTROLLER_ENDPOINT)"
  node_token="$(existing_env_value "$agent_env" GAMERAIL_NODE_TOKEN)"
  if [ -n "$agent_endpoint" ] || [ -n "$node_token" ]; then
    return 1
  fi

  api_token="$(existing_env_value "$controller_env" GAMERAIL_API_TOKEN)"
  [ -n "$api_token" ] || api_token="$(random_token)"
  controller_addr="$(existing_env_value "$controller_env" GAMERAIL_CONTROLLER_ADDR)"
  [ -n "$controller_addr" ] || controller_addr="127.0.0.1:8787"
  controller_config="$(existing_env_value "$controller_env" GAMERAIL_CONTROLLER_CONFIG)"
  [ -n "$controller_config" ] || controller_config="$config_dir/fleet.toml"
  node_id="node-$(random_token | cut -c1-12)"
  node_token="$(random_token)"
  node_name="$(hostname -s 2>/dev/null || hostname 2>/dev/null || printf 'GameRail machine')"

  auth_state="$config_dir/controller-auth.json"
  public_key="$(dashboard_public_key)"
  [ -n "$public_key" ] || die "could not retrieve the dashboard signing public key"
  auth_output="$(as_root "$bin_path" auth init --state "$auth_state")" ||
    die "failed to create controller identity"
  relay_controller_id="$(printf '%s\n' "$auth_output" | sed -n 's/^controller_id=//p')"
  claim_token="$(printf '%s\n' "$auth_output" | sed -n 's/^claim_token=//p')"

  as_root "$bin_path" bootstrap-node \
    --config "$controller_config" \
    --node-id "$node_id" \
    --token "$node_token" \
    --name "$node_name" || die "failed to create the local machine record"
  as_root chmod 0600 "$controller_config" || die "failed to protect $controller_config"

  {
    printf 'GAMERAIL_CONTROLLER_ADDR=%s\n' "$controller_addr"
    printf 'GAMERAIL_CONTROLLER_CONFIG=%s\n' "$controller_config"
    printf 'GAMERAIL_CONTROLLER_ENDPOINT=\n'
    printf 'GAMERAIL_API_TOKEN=%s\n' "$api_token"
  } | write_env "$controller_env" || die "failed to configure the local controller"
  refresh_controller_relay_env "$controller_env" "$auth_state" "$public_key"
  {
    printf 'GAMERAIL_CONTROLLER_ENDPOINT=ws://127.0.0.1:8787/agent/connect\n'
    printf 'GAMERAIL_NODE_ID=%s\n' "$node_id"
    printf 'GAMERAIL_NODE_TOKEN=%s\n' "$node_token"
    printf 'GAMERAIL_NODE_NAME=%s\n' "$node_name"
    printf 'GAMERAIL_APPLY_SYSTEM=true\n'
  } | write_env "$agent_env" || die "failed to configure the local agent"

  as_root systemctl enable gamerail-controller.service gamerail-agent.service ||
    die "failed to enable GameRail services"
  as_root systemctl restart gamerail-controller.service gamerail-agent.service ||
    die "failed to start GameRail services"
  BOOTSTRAP_RELAY_CONTROLLER_ID="$relay_controller_id"
  BOOTSTRAP_CLAIM_TOKEN="$claim_token"
  return 0
}

main() {
  require_supported_host
  install_dependencies

  local install_dir config_dir tmp bin_path
  install_dir="${GAMERAIL_INSTALL_DIR:-/usr/local/bin}"
  config_dir="${GAMERAIL_CONFIG_DIR:-/etc/gamerail}"
  bin_path="$install_dir/gamerail-agent"
  tmp="$(mktemp)"
  trap 'rm -f "${tmp:-}"' EXIT

  download_binary "$tmp"
  as_root install -d -m 0755 "$install_dir"
  as_root install -m 0755 "$tmp" "$bin_path"
  as_root install -d -m 0755 "$config_dir"
  write_initial_configs "$config_dir"
  write_configure_command "$install_dir"
  install_units "$install_dir" "$config_dir"

  log "Installed GameRail to $bin_path"
  if bootstrap_first_machine "$bin_path" "$config_dir"; then
    printf '\n%s\n' "GameRail is running. This machine is paired with its local controller."
    print_dashboard_connect "$BOOTSTRAP_RELAY_CONTROLLER_ID" "$BOOTSTRAP_CLAIM_TOKEN"
  else
    local existing_relay_id auth_state auth_output claim_token public_key controller_env
    printf '\n%s\n' "Existing GameRail configuration was preserved."
    controller_env="$config_dir/controller.env"
    auth_state="$config_dir/controller-auth.json"
    claim_token=""
    if [ ! -f "$auth_state" ]; then
      auth_output="$(as_root "$bin_path" auth init --state "$auth_state")" ||
        die "failed to create controller identity"
      existing_relay_id="$(printf '%s\n' "$auth_output" | sed -n 's/^controller_id=//p')"
      claim_token="$(printf '%s\n' "$auth_output" | sed -n 's/^claim_token=//p')"
      public_key="$(dashboard_public_key)"
      [ -n "$public_key" ] || die "could not retrieve the dashboard signing public key"
    else
      existing_relay_id="$(as_root "$bin_path" auth show --state "$auth_state" | sed -n 's/^controller_id=//p')"
      public_key="$(existing_env_value "$controller_env" GAMERAIL_DASHBOARD_PUBLIC_KEY)"
      [ -n "$public_key" ] || public_key="$(dashboard_public_key)"
      [ -n "$public_key" ] || die "could not retrieve the dashboard signing public key"
    fi
    refresh_controller_relay_env "$controller_env" "$auth_state" "$public_key"
    # Restart any already-running GameRail services so the updated binary takes effect.
    # try-restart is a no-op for units that are not currently active, so this is safe
    # on hosts where only one service (or neither) is running.
    as_root systemctl try-restart gamerail-controller.service gamerail-agent.service || true
    log "Restarted running GameRail services so the updated binary takes effect."
    print_dashboard_connect "$existing_relay_id" "$claim_token"
    if [ -z "$claim_token" ]; then
      printf '%s\n' "This controller already has identity state. To transfer ownership, run as root:"
      printf '  %s\n' "gamerail-agent auth recover --state $auth_state"
    fi
    printf '%s\n' "Adding more machines? Run this same install command on each one, then open the link it prints - the dashboard will offer to add it to your fleet."
  fi
}

main "$@"
