#!/usr/bin/env bash
# Bare-metal update: drop a fresh redhaze_run binary into /opt/redhaze/<tag>/,
# repoint /opt/redhaze/current at it, restart the systemd service, then poll
# all role health endpoints.
#
# Used by GitHub Actions deploy workflow and for manual updates from the
# server itself (after rsync'ing a new tarball).
#
# Required env:
#   REDHAZE_TAG      release tag (e.g. v0.6.2). Used as the install dir name.
#
# Honored env (all optional):
#   STAGING_DIR      = /tmp/redhaze-staging        (where rsync placed redhaze_run)
#   ROOT_DIR         = /etc/redhaze
#   ENV_FILE         = ${ROOT_DIR}/redhaze.env
#   OPT_BASE         = /opt/redhaze
#   DATA_DIR         = /var/lib/redhaze/data
#   UPLOADS_DIR      = /var/lib/redhaze/uploads
#   LOG_DIR          = /var/log/redhaze

set -euo pipefail
log() { printf '[update] %s\n' "$*"; }
fatal() { printf '[update] FATAL: %s\n' "$*" >&2; exit 1; }

TAG="${REDHAZE_TAG:?REDHAZE_TAG required}"
STAGING_DIR="${STAGING_DIR:-/tmp/redhaze-staging}"
ROOT_DIR="${ROOT_DIR:-/etc/redhaze}"
ENV_FILE="${ENV_FILE:-${ROOT_DIR}/redhaze.env}"
OPT_BASE="${OPT_BASE:-/opt/redhaze}"
DATA_DIR="${DATA_DIR:-/var/lib/redhaze/data}"
UPLOADS_DIR="${UPLOADS_DIR:-/var/lib/redhaze/uploads}"
LOG_DIR="${LOG_DIR:-/var/log/redhaze}"

SRC="${STAGING_DIR}/redhaze_run"
[ -x "${SRC}" ] || fatal "staging redhaze_run missing or not executable at ${SRC}"

# 0. Make sure runtime dirs exist. update.sh is idempotent and may run on a
#    box that has never seen bootstrap.sh; we must not assume bootstrap was
#    invoked before the first deploy.
log "ensuring runtime dirs exist"
install -d -m 0755 "${ROOT_DIR}" "${OPT_BASE}"
install -d -m 0750 "${DATA_DIR}" "${UPLOADS_DIR}" "${LOG_DIR}"

# 0b. Populate /etc/redhaze/redhaze.env on first deploy. Prefer the example
#     file shipped in the staging tarball; fall back to a minimal default.
if [ ! -f "${ENV_FILE}" ]; then
    log "no ${ENV_FILE}; seeding default values"
    if [ -f "${STAGING_DIR}/deploy/server/redhaze.env.example" ]; then
        install -m 0600 "${STAGING_DIR}/deploy/server/redhaze.env.example" "${ENV_FILE}"
    else
        cat >"${ENV_FILE}" <<'EOF'
# Auto-generated by redhaze-update.sh on first deploy. Edit as needed.
REDHAZE_DATA_DIR=/var/lib/redhaze/data
PUBLIC_HOST=
LOG_LEVEL=info
EOF
        chmod 0600 "${ENV_FILE}"
    fi
fi

# 1. Install binary under /opt/redhaze/<tag>/ and flip the current symlink.
#    Files are owned by root; the supervisor runs as root inside a systemd
#    sandbox (see redhaze.service ProtectSystem=strict + ReadWritePaths).
TARGET_DIR="${OPT_BASE}/${TAG}"
log "installing binary to ${TARGET_DIR}/redhaze_run"
install -d -m 0755 "${TARGET_DIR}"
install -m 0755 "${SRC}" "${TARGET_DIR}/redhaze_run"

log "flipping ${OPT_BASE}/current -> ${TARGET_DIR}"
ln -sfn "${TARGET_DIR}" "${OPT_BASE}/current"

# 2. Maintain the env file. Helpers:
#    - sync_env_var KEY VAL FILE  : always set KEY=VAL (overwrites existing).
#    - ensure_env_var KEY VAL FILE: only append KEY=VAL when KEY is absent.
#    - reconcile_public_url KEY SUB FILE APEX: migrate stale default URLs to
#      the canonical subdomain URL while preserving explicit custom domains.
#    We use sync for REDHAZE_VERSION (always tracks the deploy) and ensure
#    for ordinary defaults. PUBLIC_URL_* additionally gets migration logic so
#    old first deploys do not keep advertising loopback or :1808x links.
sync_env_var() {
    local key="$1" val="$2" file="$3"
    [ -n "${val}" ] || return 0
    local esc
    esc="$(printf '%s' "${val}" | sed -e 's/[\/&]/\\&/g')"
    if grep -qE "^${key}=" "${file}"; then
        sed -i "s/^${key}=.*/${key}=${esc}/" "${file}"
    else
        printf '%s=%s\n' "${key}" "${val}" >> "${file}"
    fi
}
ensure_env_var() {
    local key="$1" val="$2" file="$3"
    [ -n "${val}" ] || return 0
    if grep -qE "^${key}=" "${file}"; then
        return 0
    fi
    log "  env: appending ${key}=${val}"
    printf '%s=%s\n' "${key}" "${val}" >> "${file}"
}
reconcile_public_url() {
    local key="$1" sub="$2" file="$3" apex="$4"
    local canonical="https://${sub}.${apex}"
    local cur reason=""
    cur="$(awk -F= -v k="${key}" '$1==k{print $2; exit}' "${file}" 2>/dev/null || true)"
    case "${cur}" in
        "")
            reason="missing-or-empty"
            ;;
        "https://${apex}"|"http://${apex}"|"https://${apex}/"|"http://${apex}/")
            reason="legacy-apex"
            ;;
        http://127.0.0.1:1808*|https://127.0.0.1:1808*|http://localhost:1808*|https://localhost:1808*|http://0.0.0.0:1808*|https://0.0.0.0:1808*)
            reason="loopback-port"
            ;;
        "http://${apex}:1808"*|"https://${apex}:1808"*)
            reason="apex-port"
            ;;
    esac
    if [ -n "${reason}" ]; then
        log "  env: setting ${key}=${canonical} (${reason}; was ${cur:-<empty>})"
        sync_env_var "${key}" "${canonical}" "${file}"
    fi
}
sync_env_var REDHAZE_VERSION "${TAG#v}" "${ENV_FILE}"

# Pull PUBLIC_HOST into the local shell so we can derive subdomains. We only
# read it; we never overwrite the operator's value. Default mirrors the
# template apex.
ENV_PUBLIC_HOST="$(awk -F= '$1=="PUBLIC_HOST"{print $2; exit}' "${ENV_FILE}" 2>/dev/null || true)"
ENV_PUBLIC_HOST="${ENV_PUBLIC_HOST:-redhaze.top}"
ensure_env_var PUBLIC_HOST     "${ENV_PUBLIC_HOST}"                "${ENV_FILE}"
ensure_env_var PUBLIC_URL_ID   "https://id.${ENV_PUBLIC_HOST}"     "${ENV_FILE}"
ensure_env_var PUBLIC_URL_BBS  "https://bbs.${ENV_PUBLIC_HOST}"    "${ENV_FILE}"
ensure_env_var PUBLIC_URL_CHAT "https://chat.${ENV_PUBLIC_HOST}"   "${ENV_FILE}"
ensure_env_var PUBLIC_URL_MALL "https://mall.${ENV_PUBLIC_HOST}"   "${ENV_FILE}"
ensure_env_var PUBLIC_URL_DESK "https://desk.${ENV_PUBLIC_HOST}"   "${ENV_FILE}"
ensure_env_var PUBLIC_URL_ADS  "https://ads.${ENV_PUBLIC_HOST}"    "${ENV_FILE}"
ensure_env_var PUBLIC_URL_COLDCHAIN "https://coldchain.${ENV_PUBLIC_HOST}" "${ENV_FILE}"
ensure_env_var LOG_LEVEL       "info"                              "${ENV_FILE}"

reconcile_public_url PUBLIC_URL_ID   id   "${ENV_FILE}" "${ENV_PUBLIC_HOST}"
reconcile_public_url PUBLIC_URL_BBS  bbs  "${ENV_FILE}" "${ENV_PUBLIC_HOST}"
reconcile_public_url PUBLIC_URL_CHAT chat "${ENV_FILE}" "${ENV_PUBLIC_HOST}"
reconcile_public_url PUBLIC_URL_MALL mall "${ENV_FILE}" "${ENV_PUBLIC_HOST}"
reconcile_public_url PUBLIC_URL_DESK desk "${ENV_FILE}" "${ENV_PUBLIC_HOST}"
reconcile_public_url PUBLIC_URL_ADS  ads  "${ENV_FILE}" "${ENV_PUBLIC_HOST}"
reconcile_public_url PUBLIC_URL_COLDCHAIN coldchain "${ENV_FILE}" "${ENV_PUBLIC_HOST}"

# 3. Restart redhaze.service (Type=simple — single Go process supervisor).
log "restarting redhaze.service"
systemctl daemon-reload
systemctl restart redhaze.service

# 4. Poll every role health endpoint. Same budget as the legacy update.sh.
log "waiting for health endpoints"
declare -A HEALTH=(
    [id]=18080:/api/portal/health
    [bbs]=18081:/api/cms/health
    [chat]=18082:/api/chat/health
    [mall]=18083:/api/mall/health
    [desk]=18084:/api/desk/health
    [ads]=18085:/api/ads/health
    [coldchain]=18086:/api/coldchain/health
)
ok=1
for role in id bbs chat mall desk ads coldchain; do
    spec="${HEALTH[$role]}"
    port="${spec%%:*}"
    path="${spec#*:}"
    success=0
    for i in $(seq 1 60); do
        if curl -fsS -o /dev/null --max-time 2 "http://127.0.0.1:${port}${path}"; then
            log "  ${role} healthy after ${i}s"
            success=1
            break
        fi
        sleep 1
    done
    if [ "${success}" != "1" ]; then
        log "  ${role} did NOT become healthy"
        ok=0
    fi
done

# 5. Optional housekeeping: prune install dirs older than the previous one to
#    keep /opt/redhaze tidy without ever deleting "current" or the just-placed
#    target. Disabled by default; opt in via PRUNE_KEEP=1 (keeps current + 1).
if [ "${PRUNE_KEEP:-0}" = "1" ]; then
    log "pruning old releases (keeping current + previous)"
    current_target="$(readlink -f "${OPT_BASE}/current" || true)"
    find "${OPT_BASE}" -mindepth 1 -maxdepth 1 -type d -name 'v*' \
        | grep -v -F -- "${current_target}" \
        | sort -V \
        | head -n -1 \
        | while read -r d; do
            log "  removing ${d}"
            rm -rf -- "${d}"
        done
fi

# 6. If anything is still unhealthy, dump enough state into stdout so the CI
#    log alone is actionable — no need to SSH back in to debug.
if [ "${ok}" != "1" ]; then
    echo "[update] one or more services unhealthy, dumping diagnostics" >&2
    echo "----- systemctl status redhaze.service -----" >&2
    systemctl status redhaze.service --no-pager --lines=20 >&2 || true
    echo "----- journalctl -u redhaze.service -n 200 -----" >&2
    journalctl -u redhaze.service --no-pager -n 200 >&2 || true
    echo "----- ss -ltnp (18080..18086) -----" >&2
    ss -ltnp 2>/dev/null | grep -E '180[8-9][0-9]' >&2 || true
    echo "----- /etc/redhaze/redhaze.env (sanitized) -----" >&2
    sed -E 's/(SECRET|PASS|TOKEN|KEY)[^=]*=.*/\1=***/' "${ENV_FILE}" >&2 || true
    exit 1
fi

log "stack updated to ${TAG} and healthy"
