#!/usr/bin/env bash
# Promote ~/EtherGold-Staging → live EtherGold (03:00 / Update Now).
# Snapshots live prod+sync+VERSION to ~/EtherGold-Rollback. Restarts listen by PID
# when PROMOTE_RESTART=1. Always restarts ethergold-http (not collector) so field
# UI Python matches disk. Never sudo reboot. UART rebuild only if REBUILD_UART=1.
# After rsync: collector KillMode=process drop-in (no collector restart).
# Installs ethergold-stage.timer + ethergold-promote.timer (idempotent).
#
#   bash promote_staging.sh
#   FORCE=1 PROMOTE_RESTART=1 bash promote_staging.sh

if grep -q $'\r' "${BASH_SOURCE[0]}" 2>/dev/null; then
  sed -i 's/\r$//' "${BASH_SOURCE[0]}"
  exec bash "${BASH_SOURCE[0]}" "$@"
fi

set -euo pipefail

if [[ "$(id -un)" == root ]]; then
  echo "FATAL: do not run as root/sudo." >&2
  exit 1
fi

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=ethergold_paths.sh
source "${ROOT}/ethergold_paths.sh"
# shellcheck source=ota_lib.sh
source "${ROOT}/ota_lib.sh"

FORCE="${FORCE:-0}"
PROMOTE_RESTART="${PROMOTE_RESTART:-0}"
CONFIRM_PROMOTE_HOLD="${CONFIRM_PROMOTE_HOLD:-0}"
REBUILD_UART="${REBUILD_UART:-0}"
APPLY_DEV_TIP="${APPLY_DEV_TIP:-0}"

# Resolve OTA_CHANNEL (do not print rclone.env).
_ota_ch="${OTA_CHANNEL:-}"
if [[ -z "${_ota_ch}" && -f "${ETHERGOLD_SYNC}/rclone.env" ]]; then
  _ota_ch="$(grep -E '^OTA_CHANNEL=' "${ETHERGOLD_SYNC}/rclone.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r\"' || true)"
fi
_ota_ch="${_ota_ch:-field}"
if [[ "${APPLY_DEV_TIP}" == "1" || "${_ota_ch}" == "dev" ]]; then
  APPLY_DEV_TIP=1
fi
unset _ota_ch

# Workshop /dev tip (OTA_CHANNEL=dev): never auto-promote from the 03:00 timer.
# Promote overlays ~/EtherGold/dev and restarts ethergold-http — that kills Run EG
# soak bridges living in the HTTP cgroup (pos1 CGS 2026-09-17 ~02:28 / 03:00).
# Update Now / operator FORCE=1 still promotes. Override: AUTO_PROMOTE_DEV=1.
if [[ "${APPLY_DEV_TIP}" == "1" && "${FORCE}" != "1" && "${AUTO_PROMOTE_DEV:-0}" != "1" ]]; then
  echo "promote_staging: skip scheduled promote — OTA_CHANNEL=dev (workshop /dev tip)."
  echo "  Staging may stay pending. Promote with Update Now or: FORCE=1 bash promote_staging.sh"
  exit 0
fi

# First Promote of a new stamp: live bash is already loaded. Re-exec Staging's
# copy so post-rsync hooks (uartd drop-in) come from that stamp.
_staged_promote="${ETHERGOLD_STAGING}/sync/promote_staging.sh"
if [[ -f "${_staged_promote}" ]]; then
  _self="$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null || echo "${BASH_SOURCE[0]}")"
  _staged="$(readlink -f "${_staged_promote}" 2>/dev/null || echo "${_staged_promote}")"
  if [[ "${_self}" != "${_staged}" ]] && ! cmp -s "${_self}" "${_staged}"; then
    echo "promote_staging: re-exec Staging copy"
    exec bash "${_staged_promote}" "$@"
  fi
fi

if [[ ! -d "${ETHERGOLD_STAGING}" ]]; then
  echo "FATAL: missing staging ${ETHERGOLD_STAGING}" >&2
  exit 1
fi

if ota_promote_held "${ETHERGOLD_ROOT}" "${ETHERGOLD_STAGING}"; then
  if [[ "${CONFIRM_PROMOTE_HOLD}" != "1" ]]; then
    echo "promote_staging: hold — staging VERSION is the broken from_version. Skip."
    exit 0
  fi
  echo "promote_staging: operator confirmed hold override"
  ota_clear_promote_hold "${ETHERGOLD_ROOT}"
fi

sv="$(ota_read_version "${ETHERGOLD_STAGING}")"
lv="$(ota_live_version "${ETHERGOLD_ROOT}")"
if ota_version_is_older "${sv}" "${lv}"; then
  if [[ "${CONFIRM_DOWNGRADE}" != "1" ]]; then
    echo "promote_staging: skip — staging ${sv} older than live ${lv} (LAN/workshop ahead of SGVI)."
    echo "  Override: CONFIRM_DOWNGRADE=1 FORCE=1 bash promote_staging.sh"
    rm -f "${ETHERGOLD_PENDING_FLAG}"
    exit 0
  fi
  echo "promote_staging: operator confirmed downgrade ${lv} -> ${sv}"
fi

if [[ "${FORCE}" != "1" ]] && [[ ! -f "${ETHERGOLD_PENDING_FLAG}" ]]; then
  echo "promote_staging: nothing pending — skip"
  exit 0
fi

echo "=== EtherGold promote_staging ==="
mkdir -p "${ETHERGOLD_ROLLBACK}/prod" "${ETHERGOLD_ROLLBACK}/sync" "${ETHERGOLD_ROOT}/run"

echo "Snapshot live prod+sync → rollback"
if command -v rsync >/dev/null 2>&1; then
  [[ -d "${ETHERGOLD_PROD}" ]] && rsync -a --delete \
    --exclude logs/ --exclude run/ --exclude rclone.env --exclude '*.env' \
    --exclude progressives.csv --exclude current-game.json --exclude data/ \
    "${ETHERGOLD_PROD}/" "${ETHERGOLD_ROLLBACK}/prod/"
  [[ -d "${ETHERGOLD_SYNC}" ]] && rsync -a \
    --exclude rclone.env --exclude logs/ \
    "${ETHERGOLD_SYNC}/" "${ETHERGOLD_ROLLBACK}/sync/"
else
  [[ -d "${ETHERGOLD_PROD}" ]] && cp -a "${ETHERGOLD_PROD}/." "${ETHERGOLD_ROLLBACK}/prod/"
  [[ -d "${ETHERGOLD_SYNC}" ]] && cp -a "${ETHERGOLD_SYNC}/." "${ETHERGOLD_ROLLBACK}/sync/"
fi
if [[ -f "${ETHERGOLD_ROOT}/VERSION.txt" ]]; then
  cp -a "${ETHERGOLD_ROOT}/VERSION.txt" "${ETHERGOLD_ROLLBACK}/VERSION.txt"
fi
if [[ -f "${ETHERGOLD_PROD}/VERSION.txt" ]]; then
  cp -a "${ETHERGOLD_PROD}/VERSION.txt" "${ETHERGOLD_ROLLBACK}/prod/VERSION.txt"
fi
{
  echo "saved_at=$(date -Is)"
  echo "saved_version=$(ota_read_version "${ETHERGOLD_ROOT}")"
} > "${ETHERGOLD_ROLLBACK}/.rollback-meta"

echo "Rsync staging prod/sync → live"
if [[ -d "${ETHERGOLD_STAGING}/prod" ]]; then
  echo "Prebuilt prod fileset — skip build_prod.sh (do not --delete ELF uartd/inject)"
  rsync -a --exclude __pycache__/ --exclude '*.pyc' \
    --exclude logs/ --exclude data/ --exclude rclone.env --exclude '*.env' \
    --exclude progressives.csv --exclude current-game.json \
    --exclude pog422_uartd --exclude pog422_inject \
    "${ETHERGOLD_STAGING}/prod/" "${ETHERGOLD_PROD}/"
elif [[ -d "${ETHERGOLD_STAGING}/dev" ]]; then
  echo "FATAL: staging has /dev and no /prod. Field cannot promote a /dev tree." >&2
  echo "Publish from Production Staging (prod/ + sync/) only." >&2
  exit 1
else
  echo "FATAL: staging missing prod/ (${ETHERGOLD_STAGING})" >&2
  exit 1
fi
if [[ -d "${ETHERGOLD_STAGING}/sync" ]]; then
  rsync -a --exclude rclone.env --exclude logs/ \
    "${ETHERGOLD_STAGING}/sync/" "${ETHERGOLD_SYNC}/"
fi
for f in VERSION.txt MANIFEST.sha256 MANIFEST.total PRODUCTION_STAGING.txt; do
  if [[ -f "${ETHERGOLD_STAGING}/${f}" ]]; then
    cp -a "${ETHERGOLD_STAGING}/${f}" "${ETHERGOLD_ROOT}/${f}"
  fi
done

# Bench OTA_CHANNEL=dev: overlay tip sources onto /dev (no --delete) so soak
# stays on workshop tree while /prod still exercises the deploy-path package.
if [[ "${APPLY_DEV_TIP}" == "1" ]]; then
  if [[ -d "${ETHERGOLD_DEV}" ]]; then
    bash "${ETHERGOLD_SYNC}/apply_dev_overlay.sh" || bash "${ROOT}/apply_dev_overlay.sh"
  else
    echo "WARN: APPLY_DEV_TIP=1 but missing ${ETHERGOLD_DEV} — skip tip overlay"
  fi
fi

find "${ETHERGOLD_SYNC}" -maxdepth 1 -type f -name '*.sh' -exec sed -i 's/\r$//' {} + 2>/dev/null || true
chmod a+x "${ETHERGOLD_SYNC}"/*.sh 2>/dev/null || true

ota_apply_uartd_persist_dropin "${ETHERGOLD_SYNC}/install_uartd_persist_dropin.sh"
ota_install_hs_out_idle "${ETHERGOLD_SYNC}/install_hs_out_idle.sh"
ota_install_eg_deploy_schedule "${ETHERGOLD_SYNC}/install_pi_deploy_schedule.sh"

rm -f "${ETHERGOLD_PENDING_FLAG}"
{
  echo "promoted_at=$(date -Is)"
  echo "live_version=$(ota_read_version "${ETHERGOLD_ROOT}")"
} > "${ETHERGOLD_ROOT}/run/last-promote.txt"

echo "promote_staging ok  live=$(ota_read_version "${ETHERGOLD_ROOT}")"

# Web Promote continues tip apply (inject rebuild / USB handshake) in the same
# HTTP request. Restarting ethergold-http here kills that request before rebuild.
if [[ "${WEB_APPLY:-0}" == "1" ]]; then
  echo "promote_staging: defer HTTP restart (WEB_APPLY — tip apply finish owns it)"
else
  ota_restart_ethergold_http
fi

if [[ "${PROMOTE_RESTART}" == "1" ]]; then
  echo "Restarting listen (numeric python PIDs only)..."
  ota_restart_ethergold_listen
fi
