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

if [[ $# -gt 1 ]]; then
  echo "usage: $0 [expected-version]" >&2
  exit 2
fi

expected_version=${1:-}
systemctl_bin=${API_ROLLOUT_SYSTEMCTL_BIN:-systemctl}
curl_bin=${API_ROLLOUT_CURL_BIN:-curl}
timeout_seconds=${API_ROLLOUT_TIMEOUT_SECONDS:-45}
poll_seconds=${API_ROLLOUT_POLL_SECONDS:-0.25}
primary_unit=${API_ROLLOUT_PRIMARY_UNIT:-pushy-api.service}
candidate_unit=${API_ROLLOUT_CANDIDATE_UNIT:-pushy-api-candidate.service}
legacy_peer_unit=${API_ROLLOUT_LEGACY_PEER_UNIT:-pushy-api-peer.service}
primary_socket=${API_ROLLOUT_PRIMARY_SOCKET:-/run/pushy/metrics.sock}
candidate_socket=${API_ROLLOUT_CANDIDATE_SOCKET:-/run/pushy-api-candidate/metrics.sock}

if [[ ! $timeout_seconds =~ ^[1-9][0-9]*$ ]]; then
  echo "API_ROLLOUT_TIMEOUT_SECONDS must be a positive integer" >&2
  exit 2
fi

unit_is_active() {
  "$systemctl_bin" is-active --quiet "$1"
}

read_health() {
  "$curl_bin" --fail --silent --show-error --max-time 2 \
    --unix-socket "$1" http://localhost/health 2>/dev/null
}

health_matches() {
  local socket_path=$1
  local response
  response=$(read_health "$socket_path") || return 1
  [[ $response == *'"ok":true'* ]] || return 1
  if [[ -n $expected_version ]]; then
    [[ $response == *"\"version\":\"$expected_version\""* ]] || return 1
  fi
}

health_supports_reuse_port_release() {
  local response
  response=$(read_health "$primary_socket") || return 1
  [[ $response == *'"version":'* ]]
}

wait_ready() {
  local unit=$1
  local socket_path=$2
  local deadline=$((SECONDS + timeout_seconds))
  while (( SECONDS <= deadline )); do
    if unit_is_active "$unit" && health_matches "$socket_path"; then
      echo "$unit ready${expected_version:+ version=$expected_version}"
      return 0
    fi
    sleep "$poll_seconds"
  done
  echo "$unit did not become ready${expected_version:+ on $expected_version}" >&2
  "$systemctl_bin" status "$unit" --no-pager >&2 || true
  return 1
}

stop_if_active() {
  local unit=$1
  if unit_is_active "$unit"; then
    "$systemctl_bin" stop "$unit"
  fi
}

primary_active=false
unit_is_active "$primary_unit" && primary_active=true

if [[ $primary_active == true ]] && health_supports_reuse_port_release; then
  # Restart also replaces a stale candidate left behind by a failed rollout.
  "$systemctl_bin" restart "$candidate_unit"
  if ! wait_ready "$candidate_unit" "$candidate_socket"; then
    stop_if_active "$candidate_unit"
    exit 1
  fi

  # From this point on the candidate is the safety net. If primary fails to
  # recover, deliberately leave candidate running instead of creating an outage.
  "$systemctl_bin" restart "$primary_unit"
  wait_ready "$primary_unit" "$primary_socket"
  stop_if_active "$candidate_unit"
elif [[ $primary_active == true ]]; then
  # One-time migration from a listener that did not set SO_REUSEPORT.
  echo "migrating legacy single API listener; this rollout has one restart gap" >&2
  "$systemctl_bin" restart "$primary_unit"
  wait_ready "$primary_unit" "$primary_socket"
else
  "$systemctl_bin" start "$primary_unit"
  wait_ready "$primary_unit" "$primary_socket"
  stop_if_active "$candidate_unit"
fi

# The previous release used a permanently enabled peer. Once primary is known
# healthy it is safe to remove that compatibility process.
stop_if_active "$legacy_peer_unit"

echo "API rollout complete${expected_version:+: $expected_version}"
