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

FALLBACK_EXIT=10

ETC="${NOVIJ_RELAY_ETC:-/etc/novij/relay}"
FLAGS_DIR="${NOVIJ_RELAY_FLAGS_DIR:-/etc/novij/relay.env.d}"
STATE_DIR="${NOVIJ_RELAY_STATE_DIR:-/var/lib/novij-relay}"
ACTIVE_SLOT_FILE="${NOVIJ_RELAY_ACTIVE_SLOT_FILE:-$STATE_DIR/active-slot}"
PREVIOUS_SLOT_FILE="${NOVIJ_RELAY_PREVIOUS_SLOT_FILE:-$STATE_DIR/previous-slot}"
LAST_SWITCH_FILE="${NOVIJ_RELAY_LAST_SWITCH_FILE:-$STATE_DIR/last-switch.json}"
UPSTREAMS_CONF="${NOVIJ_RELAY_NGINX_UPSTREAMS_CONF:-/etc/nginx/conf.d/novij-relay-upstreams.conf}"
ACTIVE_UPSTREAM_CONF="${NOVIJ_RELAY_NGINX_UPSTREAM_CONF:-/etc/nginx/conf.d/novij-relay-upstream.conf}"
VHOST_BACKUP_DIR="${NOVIJ_RELAY_VHOST_BACKUP_DIR:-$STATE_DIR/nginx-vhost-backups}"
NGINX_SCAN_DIRS="${NOVIJ_RELAY_NGINX_SCAN_DIRS:-/etc/nginx/sites-enabled /etc/nginx/conf.d}"
BLUE_PORT="${NOVIJ_RELAY_BLUE_PORT:-9101}"
GREEN_PORT="${NOVIJ_RELAY_GREEN_PORT:-9102}"
SYSTEMD_TEMPLATE="${NOVIJ_RELAY_SYSTEMD_TEMPLATE:-/lib/systemd/system/novij-relay@.service}"
SYSTEMD_TEMPLATE_ALT="${NOVIJ_RELAY_SYSTEMD_TEMPLATE_ALT:-/usr/lib/systemd/system/novij-relay@.service}"
SYSTEMD_SLOT_DROPIN_DIR="${NOVIJ_RELAY_SYSTEMD_SLOT_DROPIN_DIR:-/etc/systemd/system/novij-relay@.service.d}"
PROTOCOL_EDGE_ENV="${NOVIJ_RELAY_PROTOCOL_EDGE_ENV:-$ETC/protocol-edge.env}"
PROTOCOL_EDGE_SERVICE="${NOVIJ_RELAY_PROTOCOL_EDGE_SERVICE:-novij-protocol-edge.service}"
DRAIN_TIMEOUT_SECONDS="${NOVIJ_RELAY_DRAIN_TIMEOUT_SECONDS:-30}"
READY_TIMEOUT_SECONDS="${NOVIJ_RELAY_SLOT_READY_TIMEOUT_SECONDS:-30}"
PATCHED_VHOST_FILE=""
PATCHED_VHOST_BACKUP=""

json_escape() {
  local value="${1:-}"
  value="${value//\\/\\\\}"
  value="${value//\"/\\\"}"
  value="${value//$'\n'/ }"
  printf '%s' "$value"
}

json_line() {
  local status="$1" reason="$2" location="$3"
  printf '{"status":"%s","reason":"%s","required_location":"%s"}\n' \
    "$(json_escape "$status")" "$(json_escape "$reason")" "$(json_escape "$location")"
}

event() {
  local stage="$1" status="$2" message="${3:-}"
  printf '{"event":"relay_zero_downtime","stage":"%s","status":"%s","message":"%s","ts":"%s"}\n' \
    "$(json_escape "$stage")" "$(json_escape "$status")" "$(json_escape "$message")" "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
}

write_last_switch() {
  local status="$1" stage="$2" active="${3:-}" previous="${4:-}" message="${5:-}"
  ensure_state_dir
  cat >"$LAST_SWITCH_FILE.tmp" <<EOF
{"status":"$(json_escape "$status")","stage":"$(json_escape "$stage")","active_slot":"$(json_escape "$active")","previous_slot":"$(json_escape "$previous")","message":"$(json_escape "$message")","ts":"$(date -u '+%Y-%m-%dT%H:%M:%SZ')"}
EOF
  mv "$LAST_SWITCH_FILE.tmp" "$LAST_SWITCH_FILE"
  readable_state_file "$LAST_SWITCH_FILE"
}

state_read_group() {
  if getent group novij-relay >/dev/null 2>&1; then
    printf 'novij-relay'
  fi
}

ensure_state_dir() {
  local group
  install -d -m 0755 "$STATE_DIR"
  group="$(state_read_group)"
  if [[ -n "$group" ]]; then
    chgrp "$group" "$STATE_DIR" 2>/dev/null || true
    chmod 0750 "$STATE_DIR" 2>/dev/null || true
  else
    chmod 0755 "$STATE_DIR" 2>/dev/null || true
  fi
}

readable_state_file() {
  local path="$1" group
  group="$(state_read_group)"
  if [[ -n "$group" ]]; then
    chgrp "$group" "$path" 2>/dev/null || true
    chmod 0640 "$path" 2>/dev/null || true
  else
    chmod 0644 "$path" 2>/dev/null || true
  fi
}

fallback() {
  local reason="$1" location="$2"
  json_line "blocked" "$reason" "$location"
  write_last_switch "fallback" "$reason" "" "" "$location"
  return "$FALLBACK_EXIT"
}

slot_port() {
  case "$1" in
    blue) printf '%s' "$BLUE_PORT" ;;
    green) printf '%s' "$GREEN_PORT" ;;
    *) return 1 ;;
  esac
}

opposite_slot() {
  case "$1" in
    blue) printf 'green' ;;
    green) printf 'blue' ;;
    *) printf 'blue' ;;
  esac
}

slot_url() {
	local host
	host="$(relay_listen_host)"
	if [[ "$host" == *:* ]]; then
		host="[$host]"
	fi
	printf 'http://%s:%s' "$host" "$(slot_port "$1")"
}

relay_env_value() {
  local file="$1" key="$2"
  [[ -f "$file" ]] || return 0
  awk -F= -v key="$key" '
    $1 ~ "^[[:space:]]*" key "[[:space:]]*$" {
      value = substr($0, index($0, "=") + 1)
      gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
      if ((value ~ /^".*"$/) || (value ~ /^'\''.*'\''$/)) {
        value = substr(value, 2, length(value) - 2)
      }
    }
    END { if (value != "") print value }
  ' "$file"
}

zero_downtime_enabled() {
  if [[ "${NOVIJ_RELAY_ZERO_DOWNTIME+x}" == "x" ]]; then
    [[ "${NOVIJ_RELAY_ZERO_DOWNTIME:-1}" != "0" ]]
    return $?
  fi
  local configured
  configured="$(relay_env_value "$ETC/relay.env" NOVIJ_RELAY_ZERO_DOWNTIME)"
  [[ "${configured:-1}" != "0" ]]
}

relay_public_host() {
  local raw
  raw="$(relay_env_value "$ETC/relay.env" RELAY_PUBLIC_URL)"
  [[ -n "$raw" ]] || raw="$(relay_env_value "$ETC/relay.env" APP_URL)"
  raw="${raw#http://}"
  raw="${raw#https://}"
  raw="${raw%%/*}"
  raw="${raw%%:*}"
  printf '%s' "$raw"
}

relay_listen_port() {
  local listen
  listen="$(relay_env_value "$ETC/relay.env" RELAY_GO_LISTEN)"
  [[ -n "$listen" ]] || listen="127.0.0.1:9081"
  listen="${listen#http://}"
  listen="${listen#https://}"
  if [[ "$listen" == :* ]]; then
    printf '%s' "${listen#:}"
    return 0
  fi
  printf '%s' "${listen##*:}"
}

relay_listen_host() {
	local listen host
	listen="$(relay_env_value "$ETC/relay.env" RELAY_GO_LISTEN)"
	[[ -n "$listen" ]] || listen="127.0.0.1:9081"
	listen="${listen#http://}"
	listen="${listen#https://}"
	if [[ "$listen" == :* ]]; then
		printf '127.0.0.1'
		return 0
	fi
	host="${listen%:*}"
	host="${host#[}"
	host="${host%]}"
	case "$host" in
		""|"0.0.0.0"|"::") host="127.0.0.1" ;;
	esac
	printf '%s' "$host"
}

current_active_slot() {
  if [[ -f "$ACTIVE_SLOT_FILE" ]]; then
    local slot
    slot="$(tr -d '[:space:]' <"$ACTIVE_SLOT_FILE")"
    if [[ "$slot" == "blue" || "$slot" == "green" ]]; then
      printf '%s' "$slot"
      return 0
    fi
  fi
  if systemctl is-active --quiet novij-relay@blue.service 2>/dev/null; then
    printf 'blue'
    return 0
  fi
  if systemctl is-active --quiet novij-relay@green.service 2>/dev/null; then
    printf 'green'
    return 0
  fi
  return 1
}

candidate_nginx_files() {
  local dir
  for dir in $NGINX_SCAN_DIRS; do
    [[ -d "$dir" ]] || continue
    find -L "$dir" -maxdepth 2 -type f -name '*.conf' -print 2>/dev/null
  done | sort -u
}

vhost_uses_active() {
  local file="$1"
  grep -Eq 'proxy_pass[[:space:]]+http://novij_relay_active/?;' "$file"
}

vhost_has_local_proxy() {
  local file="$1" port="$2"
  grep -Eq "proxy_pass[[:space:]]+http://(127\\.0\\.0\\.1|localhost|0\\.0\\.0\\.0):${port}/?;" "$file"
}

find_relay_vhost() {
  local port host file
  port="$(relay_listen_port)"
  host="$(relay_public_host)"
  while IFS= read -r file; do
    vhost_uses_active "$file" && { printf '%s' "$file"; return 0; }
  done < <(candidate_nginx_files)
  while IFS= read -r file; do
    vhost_has_local_proxy "$file" "$port" && { printf '%s' "$file"; return 0; }
  done < <(candidate_nginx_files)
  if [[ -n "$host" ]]; then
    while IFS= read -r file; do
      if grep -Eq "server_name[[:space:]].*(^|[[:space:]])${host//./\\.}([[:space:];]|$)" "$file" &&
        grep -Eq 'X-Novij-Route-Mode|novij-relay|relay-go' "$file"; then
        printf '%s' "$file"
        return 0
      fi
    done < <(candidate_nginx_files)
  fi
  return 1
}

write_static_upstreams() {
	local host
	host="$(relay_listen_host)"
  install -d -m 0755 "$(dirname "$UPSTREAMS_CONF")"
  cat >"$UPSTREAMS_CONF.tmp" <<EOF
upstream novij_relay_blue {
    server $host:$BLUE_PORT max_fails=3 fail_timeout=5s;
    keepalive 64;
}

upstream novij_relay_green {
    server $host:$GREEN_PORT max_fails=3 fail_timeout=5s;
    keepalive 64;
}
EOF
  mv "$UPSTREAMS_CONF.tmp" "$UPSTREAMS_CONF"
  chmod 0644 "$UPSTREAMS_CONF"
}

write_active_upstream() {
	local slot="$1" port host
	port="$(slot_port "$slot")"
	host="$(relay_listen_host)"
  install -d -m 0755 "$(dirname "$ACTIVE_UPSTREAM_CONF")"
  cat >"$ACTIVE_UPSTREAM_CONF.tmp" <<EOF
upstream novij_relay_active {
    server $host:$port max_fails=3 fail_timeout=5s;
    keepalive 64;
}
EOF
  mv "$ACTIVE_UPSTREAM_CONF.tmp" "$ACTIVE_UPSTREAM_CONF"
  chmod 0644 "$ACTIVE_UPSTREAM_CONF"
}

patch_vhost_to_active() {
  local file="$1" port="$2"
  vhost_uses_active "$file" && return 0
  vhost_has_local_proxy "$file" "$port" || return 1
  install -d -m 0750 "$VHOST_BACKUP_DIR"
  PATCHED_VHOST_FILE="$file"
  PATCHED_VHOST_BACKUP="$VHOST_BACKUP_DIR/$(basename "$file").novij-relay-pre-bluegreen.$(date +%s).$$.bak"
  cp -a "$file" "$PATCHED_VHOST_BACKUP"
  sed -i -E "s#proxy_pass[[:space:]]+http://(127\\.0\\.0\\.1|localhost|0\\.0\\.0\\.0):${port}/?;#proxy_pass http://novij_relay_active;#g" "$file"
}

restore_patched_vhost() {
  if [[ -n "$PATCHED_VHOST_FILE" && -n "$PATCHED_VHOST_BACKUP" && -f "$PATCHED_VHOST_BACKUP" ]]; then
    cp -a "$PATCHED_VHOST_BACKUP" "$PATCHED_VHOST_FILE" || true
  fi
}

nginx_test() {
  nginx -t >/dev/null
}

reload_nginx() {
  systemctl reload nginx
}

prepare_nginx_config() {
  local slot="$1" vhost port
  command -v nginx >/dev/null 2>&1 || return "$FALLBACK_EXIT"
  vhost="$(find_relay_vhost || true)"
  [[ -n "$vhost" ]] || return "$FALLBACK_EXIT"
  port="$(relay_listen_port)"
  write_static_upstreams
  write_active_upstream "$slot"
  if ! vhost_uses_active "$vhost"; then
    patch_vhost_to_active "$vhost" "$port" || return "$FALLBACK_EXIT"
  fi
  if ! nginx_test; then
    restore_patched_vhost
    return "$FALLBACK_EXIT"
  fi
}

switch_nginx_to_slot() {
  local slot="$1"
  write_active_upstream "$slot"
  nginx_test
  reload_nginx
}

protocol_edge_enabled() {
	[[ -s "$PROTOCOL_EDGE_ENV" ]] && systemctl cat "$PROTOCOL_EDGE_SERVICE" >/dev/null 2>&1
}

switch_protocol_edge_to_slot() {
	local slot="$1" previous="" port output deadline
	previous="$(tr -d '[:space:]' <"$ACTIVE_SLOT_FILE" 2>/dev/null || true)"
	ensure_state_dir
	printf '%s\n' "$slot" >"$ACTIVE_SLOT_FILE"
	readable_state_file "$ACTIVE_SLOT_FILE"
	if ! systemctl start "$PROTOCOL_EDGE_SERVICE"; then
		if [[ "$previous" == "blue" || "$previous" == "green" ]]; then
			printf '%s\n' "$previous" >"$ACTIVE_SLOT_FILE"
			readable_state_file "$ACTIVE_SLOT_FILE"
		else
			rm -f "$ACTIVE_SLOT_FILE"
		fi
		systemctl start "$PROTOCOL_EDGE_SERVICE" >/dev/null 2>&1 || true
		return 1
	fi
	output="$(relay_env_value "$PROTOCOL_EDGE_ENV" NOVIJ_PROTOCOL_EDGE_OUTPUT)"
	[[ -n "$output" ]] || output="/opt/novij-mail/traefik/dynamic/novij-protocol.yml"
	port="$(slot_port "$slot")"
	deadline=$((SECONDS + 5))
	while (( SECONDS <= deadline )); do
		grep -Eq "url:[[:space:]]*\\\"http://[^\\\"]*:${port}\\\"" "$output" 2>/dev/null && return 0
		sleep 1
	done
	return 1
}

ROUTING_MODE=""

prepare_routing_config() {
	local slot="$1"
	if command -v nginx >/dev/null 2>&1 && [[ -n "$(find_relay_vhost || true)" ]]; then
		ROUTING_MODE="nginx"
		prepare_nginx_config "$slot"
		return $?
	fi
	if protocol_edge_enabled; then
		ROUTING_MODE="protocol-edge"
		return 0
	fi
	return "$FALLBACK_EXIT"
}

switch_routing_to_slot() {
	case "$ROUTING_MODE" in
		nginx) switch_nginx_to_slot "$1" ;;
		protocol-edge) switch_protocol_edge_to_slot "$1" ;;
		*) return 1 ;;
	esac
}

write_slot_env() {
	local slot="$1" port host
	port="$(slot_port "$slot")"
	host="$(relay_listen_host)"
  install -d -m 0750 "$FLAGS_DIR"
  cat >"$FLAGS_DIR/slot-$slot.conf" <<EOF
RELAY_SLOT=$slot
RELAY_GO_LISTEN=$host:$port
EOF
  chmod 0640 "$FLAGS_DIR/slot-$slot.conf"
}

curl_ok() {
  local url="$1"
  curl -fsS --connect-timeout 2 --max-time 5 "$url" >/dev/null
}

relay_public_base_url() {
  local base
  base="$(relay_env_value "$ETC/relay.env" RELAY_PUBLIC_URL)"
  [[ -n "$base" ]] || base="$(relay_env_value "$ETC/relay.env" APP_URL)"
  printf '%s\n' "${base%/}"
}

wait_routed_ready() {
  [[ "$ROUTING_MODE" == "protocol-edge" ]] || return 0
  local slot="$1" base target expected_version public_version deadline
  base="$(relay_public_base_url)"
  [[ "$base" == http://* || "$base" == https://* ]] || return 1
  target="$(slot_url "$slot")"
  expected_version="$(curl -fsS --connect-timeout 2 --max-time 5 "$target/version" | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)"
  [[ -n "$expected_version" ]] || return 1
  deadline=$((SECONDS + READY_TIMEOUT_SECONDS))
  while (( SECONDS <= deadline )); do
    public_version="$(curl -fsS --connect-timeout 2 --max-time 5 "$base/version" 2>/dev/null | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1 || true)"
    if curl_ok "$base/healthz" && [[ "$public_version" == "$expected_version" ]]; then
      return 0
    fi
    sleep 1
  done
  return 1
}

restore_routing_after_failed_switch() {
  local previous="$1"
  if [[ "$previous" == "blue" || "$previous" == "green" ]]; then
    switch_routing_to_slot "$previous"
    return $?
  fi
  if [[ "$ROUTING_MODE" == "protocol-edge" ]]; then
    rm -f "$ACTIVE_SLOT_FILE"
    systemctl start "$PROTOCOL_EDGE_SERVICE"
    return $?
  fi
  restore_patched_vhost
}

wait_ready() {
  local slot="$1" base deadline
  base="$(slot_url "$slot")"
  deadline=$((SECONDS + READY_TIMEOUT_SECONDS))
  while (( SECONDS <= deadline )); do
    if curl_ok "$base/healthz" && curl_ok "$base/deploy/readyz" && curl_ok "$base/version" && curl_ok "$base/peer/ping"; then
      return 0
    fi
    sleep 1
  done
  return 1
}

drain_old_slot() {
  local slot="$1" base deadline body inflight
  base="$(slot_url "$slot")"
  curl -fsS --connect-timeout 2 --max-time 5 -X POST "$base/internal/drain/start" >/dev/null || true
  deadline=$((SECONDS + DRAIN_TIMEOUT_SECONDS))
  while (( SECONDS <= deadline )); do
    body="$(curl -fsS --connect-timeout 2 --max-time 5 "$base/internal/inflight" 2>/dev/null || true)"
    inflight="$(printf '%s' "$body" | sed -n 's/.*"inflight":[[:space:]]*\([0-9][0-9]*\).*/\1/p')"
    [[ -z "$inflight" || "$inflight" == "0" ]] && return 0
    sleep 1
  done
  return 1
}

require_deploy_runtime() {
  zero_downtime_enabled || return "$FALLBACK_EXIT"
  [[ -f "$ETC/relay.env" ]] || return "$FALLBACK_EXIT"
  [[ -f "$SYSTEMD_TEMPLATE" || -f "$SYSTEMD_TEMPLATE_ALT" ]] || return 1
  command -v systemctl >/dev/null 2>&1 || return 1
  command -v curl >/dev/null 2>&1 || return 1
}

single_service_has_install_auth_bypass() {
  systemctl cat novij-relay.service 2>/dev/null | awk '
    /^\[Service\]/ { in_service = 1; next }
    /^\[/ { in_service = 0 }
    in_service && /^[[:space:]]*ExecStartPre=[[:space:]]*$/ { found = 1 }
    END { exit found ? 0 : 1 }
  '
}

ensure_slot_install_auth_compat() {
  [[ -f "$ETC/install-authorization.json" ]] && return 0
  [[ "${NOVIJ_RELAY_INHERIT_SINGLE_SERVICE_AUTH_BYPASS:-1}" != "0" ]] && single_service_has_install_auth_bypass || return 0
  install -d -m 0755 "$SYSTEMD_SLOT_DROPIN_DIR"
  cat >"$SYSTEMD_SLOT_DROPIN_DIR/10-live-compat.conf.tmp" <<'EOF'
[Service]
ExecStartPre=
EOF
  mv "$SYSTEMD_SLOT_DROPIN_DIR/10-live-compat.conf.tmp" "$SYSTEMD_SLOT_DROPIN_DIR/10-live-compat.conf"
  chmod 0644 "$SYSTEMD_SLOT_DROPIN_DIR/10-live-compat.conf"
  event "slot_install_auth_compat" "warn" "inherited existing novij-relay.service ExecStartPre override for blue/green slots"
}

record_active_slot() {
  local active="$1" previous="${2:-}"
  ensure_state_dir
  printf '%s\n' "$active" >"$ACTIVE_SLOT_FILE"
  readable_state_file "$ACTIVE_SLOT_FILE"
  if [[ -n "$previous" ]]; then
    printf '%s\n' "$previous" >"$PREVIOUS_SLOT_FILE"
    readable_state_file "$PREVIOUS_SLOT_FILE"
  fi
}

deploy() {
  if ! require_deploy_runtime; then
    fallback "ZERO_DOWNTIME_RUNTIME_NOT_AVAILABLE" "systemd, curl, $ETC/relay.env and novij-relay@.service"
    return $?
  fi
  local active="" target initial="false"
  active="$(current_active_slot || true)"
  if [[ -z "$active" ]]; then
    target="blue"
    initial="true"
  else
    target="$(opposite_slot "$active")"
  fi
  # Keep nginx on the currently serving slot while the candidate starts and
  # passes readiness. Writing the target upstream before wait_ready() makes a
  # failed deployment route production traffic to the slot we then stop.
	if ! prepare_routing_config "${active:-$target}"; then
		fallback "RELAY_ROUTING_NOT_BLUEGREEN_READY" "nginx vhost or novij-protocol-edge.service"
    return $?
  fi
  event "start" "ok" "active=${active:-none} target=$target initial=$initial"
  write_slot_env "$target"
  ensure_slot_install_auth_compat
  systemctl daemon-reload
  systemctl enable "novij-relay@$target.service" >/dev/null 2>&1 || true
  if ! systemctl restart "novij-relay@$target.service"; then
    event "target_start" "fail" "systemd could not start novij-relay@$target.service"
    restore_patched_vhost
    write_last_switch "failed" "target_start" "$active" "$target" "target service did not start"
    return 1
  fi
  if ! wait_ready "$target"; then
    event "target_ready" "fail" "$target did not pass health/deploy-ready/version/peer smoke"
    systemctl stop "novij-relay@$target.service" >/dev/null 2>&1 || true
    restore_patched_vhost
    write_last_switch "failed" "target_ready" "$active" "$target" "target did not become ready"
    return 1
  fi
	if ! switch_routing_to_slot "$target"; then
		event "routing_switch" "fail" "$ROUTING_MODE rejected target=$target"
		if [[ -n "$active" ]]; then
			switch_routing_to_slot "$active" || event "routing_restore" "fail" "could not restore active=$active after failed target=$target switch"
    fi
    systemctl stop "novij-relay@$target.service" >/dev/null 2>&1 || true
    restore_patched_vhost
		write_last_switch "failed" "routing_switch" "$active" "$target" "routing switch failed"
    return 1
  fi
  if ! wait_routed_ready "$target"; then
    event "routing_health" "fail" "$ROUTING_MODE did not expose target=$target through the public route"
    restore_routing_after_failed_switch "$active" || event "routing_restore" "fail" "could not restore active=${active:-legacy} after public route failure"
    systemctl stop "novij-relay@$target.service" >/dev/null 2>&1 || true
    restore_patched_vhost
    write_last_switch "failed" "routing_health" "$active" "$target" "public route did not become ready"
    return 1
  fi
  record_active_slot "$target" "$active"
  systemctl disable novij-relay.service >/dev/null 2>&1 || true
  systemctl stop novij-relay.service >/dev/null 2>&1 || true
  if [[ -n "$active" && "$active" != "$target" ]] && systemctl is-active --quiet "novij-relay@$active.service" 2>/dev/null; then
    drain_old_slot "$active" || event "drain_old" "warn" "$active still has inflight requests after timeout"
    systemctl stop "novij-relay@$active.service" >/dev/null 2>&1 || true
  fi
  write_last_switch "ok" "deploy" "$target" "$active" "zero-downtime switch complete"
  event "complete" "ok" "active=$target previous=${active:-none}"
}

rollback() {
  if ! require_deploy_runtime; then
    fallback "ZERO_DOWNTIME_RUNTIME_NOT_AVAILABLE" "systemd, curl, $ETC/relay.env and novij-relay@.service"
    return $?
  fi
  local active previous
  active="$(current_active_slot || true)"
  previous="$(tr -d '[:space:]' <"$PREVIOUS_SLOT_FILE" 2>/dev/null || true)"
  if [[ "$previous" != "blue" && "$previous" != "green" ]]; then
    json_line "blocked" "PREVIOUS_SLOT_NOT_FOUND" "$PREVIOUS_SLOT_FILE"
    return 1
  fi
	if ! prepare_routing_config "$previous"; then
		fallback "RELAY_ROUTING_NOT_BLUEGREEN_READY" "nginx vhost or novij-protocol-edge.service"
    return $?
  fi
  write_slot_env "$previous"
  ensure_slot_install_auth_compat
  systemctl daemon-reload
  if ! systemctl restart "novij-relay@$previous.service"; then
    restore_patched_vhost
    write_last_switch "failed" "rollback_start" "$active" "$previous" "previous service did not start"
    return 1
  fi
  wait_ready "$previous"
	switch_routing_to_slot "$previous"
  record_active_slot "$previous" "$active"
  if [[ -n "$active" && "$active" != "$previous" ]] && systemctl is-active --quiet "novij-relay@$active.service" 2>/dev/null; then
    drain_old_slot "$active" || true
    systemctl stop "novij-relay@$active.service" >/dev/null 2>&1 || true
  fi
  write_last_switch "ok" "rollback" "$previous" "$active" "rollback complete"
  event "rollback_complete" "ok" "active=$previous previous=${active:-none}"
}

can_run() {
  require_deploy_runtime || {
    json_line "blocked" "ZERO_DOWNTIME_RUNTIME_NOT_AVAILABLE" "systemd, curl, $ETC/relay.env and novij-relay@.service"
    return 1
  }
	if ! { command -v nginx >/dev/null 2>&1 && [[ -n "$(find_relay_vhost || true)" ]]; } && ! protocol_edge_enabled; then
		json_line "blocked" "RELAY_ROUTING_NOT_BLUEGREEN_READY" "nginx vhost or novij-protocol-edge.service"
		return 1
	fi
  json_line "ok" "ZERO_DOWNTIME_READY" "$ACTIVE_SLOT_FILE"
}

case "${1:-deploy}" in
  --can-run|can-run)
    can_run
    ;;
  deploy|switch)
    deploy
    ;;
  rollback)
    rollback
    ;;
  *)
    echo "usage: $0 [--can-run|deploy|rollback]" >&2
    exit 2
    ;;
esac
