#!/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 nftables wireguard-tools"
  fi

  log "Installing GameRail networking 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 nftables wireguard-tools
      ;;
    dnf)
      as_root dnf install -y ca-certificates curl iproute nftables wireguard-tools
      ;;
    yum)
      as_root yum install -y ca-certificates curl iproute nftables wireguard-tools
      ;;
    pacman)
      as_root pacman -Sy --needed --noconfirm ca-certificates curl iproute2 nftables wireguard-tools
      ;;
    apk)
      as_root apk add --no-cache ca-certificates curl iproute2 nftables wireguard-tools
      ;;
    zypper)
      as_root zypper --non-interactive install ca-certificates curl iproute2 nftables wireguard-tools
      ;;
  esac

  # Podman powers the game-host module. Install it best-effort so machines are
  # game-ready out of the box, but never fail the install over it: the
  # dashboard surfaces a missing Podman capability per machine.
  log "Installing Podman for the game-host module (best effort)"
  case "$pm" in
    apt-get) as_root apt-get install -y podman uidmap slirp4netns || log "Podman could not be installed; game hosting will be unavailable on this machine until it is" ;;
    dnf) as_root dnf install -y podman || log "Podman could not be installed; game hosting will be unavailable on this machine until it is" ;;
    yum) as_root yum install -y podman || log "Podman could not be installed; game hosting will be unavailable on this machine until it is" ;;
    pacman) as_root pacman -Sy --needed --noconfirm podman || log "Podman could not be installed; game hosting will be unavailable on this machine until it is" ;;
    apk) as_root apk add --no-cache podman || log "Podman could not be installed; game hosting will be unavailable on this machine until it is" ;;
    zypper) as_root zypper --non-interactive install podman || log "Podman could not be installed; game hosting will be unavailable on this machine until it is" ;;
  esac
}

# 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}"

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 api_token relay_controller_id relay_ws_base encoded_relay_url
  api_token="$1"
  relay_controller_id="${2:-}"
  [ -n "$api_token" ] || return 0

  # Derive WebSocket base URL from RELAY_BASE (https->wss, http->ws)
  case "$RELAY_BASE" in
    https://*) relay_ws_base="wss${RELAY_BASE#https}" ;;
    http://*) relay_ws_base="ws${RELAY_BASE#http}" ;;
    *) relay_ws_base="$RELAY_BASE" ;;
  esac

  # Use the token directly in URLs only when it is URL-safe (unreserved
  # characters: A-Z a-z 0-9 - . _ ~). Tokens are currently hex so this
  # always matches; fall back to manual-only if the format ever changes.
  case "$api_token" in
    *[!A-Za-z0-9._~-]*)
      # Token not URL-safe; print relay link without token and manual instructions
      if [ -n "$relay_controller_id" ]; then
        printf '\n%s\n' "Connect the dashboard with the relay link:"
        printf '  %s\n' "https://dashboard.gamerail.app/#controller=$(printf '%s' "$RELAY_BASE/c/$relay_controller_id" | sed 's|:|%3A|g; s|/|%2F|g')&token=$api_token"
      fi
      printf '\n%s\n' "Or open https://dashboard.gamerail.app and connect manually with:"
      printf '  %-16s %s\n' "Controller URL:" "http://127.0.0.1:8787"
      printf '  %-16s %s\n' "Controller token:" "$api_token"
      ;;
    *)
      if [ -n "$relay_controller_id" ]; then
        # Percent-encode the full controller URL, e.g. https://relay.gamerail.app/c/<id>
        encoded_relay_url="$(printf '%s' "$RELAY_BASE/c/$relay_controller_id" | sed 's|:|%3A|g; s|/|%2F|g')"
        printf '\n%s\n' "Open this link to connect the dashboard:"
        printf '  %s\n' "https://dashboard.gamerail.app/#controller=${encoded_relay_url}&token=$api_token"
        printf '\n%s\n' "Re-running the installer prints this link again at any time."
      else
        printf '\n%s\n' "From your computer, open an SSH tunnel:"
        printf '  %s\n' "ssh -L 8787:127.0.0.1:8787 USER@THIS_SERVER"
        printf '\n%s\n' "Then connect the dashboard with one click (keep the tunnel open):"
        printf '  %s\n' "https://dashboard.gamerail.app/#controller=http%3A%2F%2F127.0.0.1%3A8787&token=$api_token"
        printf '\n%s\n' "Or connect manually at https://dashboard.gamerail.app with:"
        printf '  %-16s %s\n' "Controller URL:" "http://127.0.0.1:8787"
        printf '  %-16s %s\n' "Controller token:" "$api_token"
      fi
      ;;
  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_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_API_TOKEN:-}"

  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] [--api-token TOKEN]
  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="${GAMERAIL_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 ;;
      --api-token) api_token="$(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' "Dashboard token is stored in $CONTROLLER_ENV as GAMERAIL_API_TOKEN"
}

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)"
trap 'rm -f "$tmp" "$sums"' 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" "$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 relay_ws_base
  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')"

  # Generate relay controller id: 'c' + 31 hex chars = 32-char lowercase hex id
  relay_controller_id="c$(random_token | cut -c1-31)"

  # Derive WebSocket base URL from RELAY_BASE
  case "$RELAY_BASE" in
    https://*) relay_ws_base="wss${RELAY_BASE#https}" ;;
    http://*) relay_ws_base="ws${RELAY_BASE#http}" ;;
    *) relay_ws_base="$RELAY_BASE" ;;
  esac

  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"
    printf 'GAMERAIL_RELAY_URL=%s\n' "$relay_ws_base"
    printf 'GAMERAIL_RELAY_CONTROLLER_ID=%s\n' "$relay_controller_id"
  } | write_env "$controller_env" || die "failed to configure the local controller"
  {
    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_API_TOKEN="$api_token"
  BOOTSTRAP_RELAY_CONTROLLER_ID="$relay_controller_id"
  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_API_TOKEN" "$BOOTSTRAP_RELAY_CONTROLLER_ID"
  else
    local existing_token existing_relay_id
    printf '\n%s\n' "Existing GameRail configuration was preserved."
    existing_token="$(existing_env_value "$config_dir/controller.env" GAMERAIL_API_TOKEN)"
    existing_relay_id="$(existing_env_value "$config_dir/controller.env" GAMERAIL_RELAY_CONTROLLER_ID)"
    # If relay id is missing from an older install, generate and append it now
    if [ -z "$existing_relay_id" ]; then
      existing_relay_id="c$(random_token | cut -c1-31)"
      local relay_ws_base
      case "$RELAY_BASE" in
        https://*) relay_ws_base="wss${RELAY_BASE#https}" ;;
        http://*) relay_ws_base="ws${RELAY_BASE#http}" ;;
        *) relay_ws_base="$RELAY_BASE" ;;
      esac
      printf 'GAMERAIL_RELAY_URL=%s\nGAMERAIL_RELAY_CONTROLLER_ID=%s\n' \
        "$relay_ws_base" "$existing_relay_id" | as_root tee -a "$config_dir/controller.env" >/dev/null ||
        die "failed to append relay config to controller.env"
    fi
    # 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_token" "$existing_relay_id"
    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 "$@"
