#!/usr/bin/env bash
#
# verify-restore.sh
# -----------------
# Smoke-test a backup file by booting the API server against a copy of it
# and asserting that admin login succeeds.
#
# Workflow:
#   1. Pick a free high port.
#   2. Copy the backup file to a temp directory.
#   3. Set DB_PATH to the copy, PORT to the free port, and spawn the
#      server with `node src/index.js` in the background.
#   4. Poll /api/auth/login with admin creds until 200 OR timeout.
#   5. Send SIGTERM to the server, wait for it to exit, clean up the
#      temp directory.
#   6. Exit 0 on success, 1 on failure.
#
# Usage:
#   ./verify-restore.sh /backups/school-20260716-120000.db
#   ./verify-restore.sh                       # auto-pick the most recent
#
# This script is meant to be run on the production Ubuntu 22.04 host.
# It is NOT a Windows script. On a Windows dev box, only
# `bash -n verify-restore.sh` (syntax check) is meaningful.

set -eu

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
SERVER_DIR="${PROJECT_ROOT}/server"

LOG_DIR="${LOG_DIR:-${PROJECT_ROOT}/logs}"
LOG_FILE="${LOG_DIR}/backup.log"
VERIFY_LOG="${LOG_DIR}/verify-restore.log"

# Test parameters (override on the env if you must):
ADMIN_EMAIL="${ADMIN_EMAIL:-admin@school.com}"
ADMIN_PASSWORD="${ADMIN_PASSWORD:-admin123}"
STARTUP_TIMEOUT="${STARTUP_TIMEOUT:-15}"   # seconds to wait for server
LOGIN_TIMEOUT="${LOGIN_TIMEOUT:-10}"       # seconds for the login probe

# --- Pre-flight --------------------------------------------------------------

if ! command -v curl >/dev/null 2>&1; then
  echo "ERROR: curl not found in PATH" >&2
  exit 1
fi
if ! command -v node >/dev/null 2>&1; then
  echo "ERROR: node not found in PATH" >&2
  exit 1
fi
if [ ! -d "${SERVER_DIR}" ]; then
  echo "ERROR: server dir not found: ${SERVER_DIR}" >&2
  exit 1
fi

# Pick the backup file: positional arg, or newest in /backups.
if [ $# -ge 1 ]; then
  BACKUP_FILE="$1"
else
  if [ -d "/backups" ]; then
    # shellcheck disable=SC2012
    BACKUP_FILE="$(ls -1t /backups/school-*.db 2>/dev/null | head -n 1 || true)"
  fi
  if [ -z "${BACKUP_FILE:-}" ]; then
    echo "ERROR: no backup file given and /backups/school-*.db not found" >&2
    echo "usage: $0 <backup-file>" >&2
    exit 64
  fi
fi

if [ ! -f "${BACKUP_FILE}" ]; then
  echo "ERROR: backup file not found: ${BACKUP_FILE}" >&2
  exit 1
fi

mkdir -p "${LOG_DIR}"
touch "${VERIFY_LOG}"

log() {
  local msg="[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [verify-restore] $*"
  echo "${msg}" | tee -a "${VERIFY_LOG}" >&2
}

# --- Pick a free port --------------------------------------------------------
# Use Node to ask the kernel for a free ephemeral port. We prefer Node over
# python because Node is guaranteed to be present (the project requires it
# to run, and we just used it above) and there is no Python-on-Windows
# aliasing ambiguity to worry about. Falls back to a constant high port
# if neither python nor node is available.
pick_free_port() {
  if command -v node >/dev/null 2>&1; then
    node -e 'const s=require("net").createServer(); s.listen(0,()=>{process.stdout.write(String(s.address().port));s.close();});'
    return 0
  fi
  if command -v python3 >/dev/null 2>&1; then
    python3 -c 'import socket; s=socket.socket(); s.bind(("",0)); print(s.getsockname()[1]); s.close()'
    return 0
  fi
  if command -v python >/dev/null 2>&1; then
    # On Windows the Microsoft Store "python" alias can hijack the binary.
    # Probe it: if 'python --version' fails, fall through to the constant.
    if python --version >/dev/null 2>&1; then
      python -c 'import socket; s=socket.socket(); s.bind(("",0)); print(s.getsockname()[1]); s.close()'
      return 0
    fi
  fi
  echo "30101"
}

TEST_PORT="$(pick_free_port)"
log "using test port ${TEST_PORT}"

# --- Stage a temp working dir for the test DB --------------------------------
WORK_DIR="$(mktemp -d -t africa-alert-verify.XXXXXX)"
trap 'cleanup' EXIT INT TERM

TEST_DB="${WORK_DIR}/school.db"

cleanup() {
  local rc=$?
  if [ -n "${SERVER_PID:-}" ] && kill -0 "${SERVER_PID}" 2>/dev/null; then
    log "stopping test server pid=${SERVER_PID}"
    kill "${SERVER_PID}" 2>/dev/null || true
    # Give it 5s to flush and exit; then SIGKILL.
    for _ in 1 2 3 4 5; do
      kill -0 "${SERVER_PID}" 2>/dev/null || break
      sleep 1
    done
    if kill -0 "${SERVER_PID}" 2>/dev/null; then
      kill -9 "${SERVER_PID}" 2>/dev/null || true
    fi
  fi
  if [ -d "${WORK_DIR}" ]; then
    # Best-effort cleanup. On Windows the SQLite -wal/-shm sidecars and
    # even the .db file itself can be held open briefly by the OS after
    # the Node child exits, so rm returns EBUSY. That's fine — the temp
    # dir is in the OS-managed scratch space and will be reclaimed.
    rm -rf "${WORK_DIR}" 2>/dev/null || log "WARN: temp dir cleanup failed (rc=$?); ignoring"
  fi
  if [ "${rc}" -eq 0 ]; then
    log "verify-restore PASSED"
  else
    log "verify-restore FAILED (rc=${rc})"
  fi
}

cp -f "${BACKUP_FILE}" "${TEST_DB}"
# If the backup has sidecars, copy them too so the test reflects a faithful
# restore.
for ext in wal shm; do
  if [ -f "${BACKUP_FILE}-${ext}" ]; then
    cp -f "${BACKUP_FILE}-${ext}" "${TEST_DB}-${ext}"
  fi
done

log "staged test db at ${TEST_DB}"

# --- Spawn the server -------------------------------------------------------
SERVER_STDERR="${WORK_DIR}/server.stderr.log"
SERVER_STDOUT="${WORK_DIR}/server.stdout.log"

# Run in background; capture PIDs. We use bash to redirect so the file
# handles are owned by the subshell, not this script.
(
  cd "${SERVER_DIR}"
  DB_PATH="${TEST_DB}" \
  PORT="${TEST_PORT}" \
  NODE_ENV=development \
    node src/index.js \
      >"${SERVER_STDOUT}" 2>"${SERVER_STDERR}"
) &
SERVER_PID=$!
log "spawned test server pid=${SERVER_PID}"

# --- Wait for readiness ------------------------------------------------------
# Probe the login endpoint with bad creds; the server is ready when we
# get a 401 (route mounted, DB queryable) instead of a connection error.
ready=0
deadline=$(( $(date +%s) + STARTUP_TIMEOUT ))
while [ "$(date +%s)" -lt "${deadline}" ]; do
  if ! kill -0 "${SERVER_PID}" 2>/dev/null; then
    log "ERROR: server process exited before becoming ready"
    log "--- server stdout ---"
    sed 's/^/    /' "${SERVER_STDOUT}" | tee -a "${VERIFY_LOG}" >&2 || true
    log "--- server stderr ---"
    sed 's/^/    /' "${SERVER_STDERR}" | tee -a "${VERIFY_LOG}" >&2 || true
    exit 1
  fi
  CODE="$(curl -s -o /dev/null -w '%{http_code}' \
    -X POST \
    -H 'Content-Type: application/json' \
    -d '{"email":"__probe__","password":"__probe__"}' \
    --max-time 2 \
    "http://127.0.0.1:${TEST_PORT}/api/auth/login" || echo 000)"
  case "${CODE}" in
    400|401) ready=1; break ;;  # 400=missing creds, 401=invalid creds — both mean route+DB are alive
    000) sleep 0.3 ;;           # connection refused / timeout
    *) sleep 0.3 ;;             # anything else (500?) — keep trying
  esac
done

if [ "${ready}" -ne 1 ]; then
  log "ERROR: server did not become ready within ${STARTUP_TIMEOUT}s"
  log "--- server stdout ---"
  sed 's/^/    /' "${SERVER_STDOUT}" | tee -a "${VERIFY_LOG}" >&2 || true
  log "--- server stderr ---"
  sed 's/^/    /' "${SERVER_STDERR}" | tee -a "${VERIFY_LOG}" >&2 || true
  exit 1
fi
log "server ready on :${TEST_PORT}"

# --- Real login probe -------------------------------------------------------
log "probing /api/auth/login as ${ADMIN_EMAIL}"
LOGIN_RESPONSE="$(mktemp)"
LOGIN_DEADLINE=$(( $(date +%s) + LOGIN_TIMEOUT ))
LOGIN_CODE=000
while [ "$(date +%s)" -lt "${LOGIN_DEADLINE}" ]; do
  LOGIN_CODE="$(curl -s -o "${LOGIN_RESPONSE}" -w '%{http_code}' \
    -X POST \
    -H 'Content-Type: application/json' \
    -d "{\"email\":\"${ADMIN_EMAIL}\",\"password\":\"${ADMIN_PASSWORD}\"}" \
    --max-time 5 \
    "http://127.0.0.1:${TEST_PORT}/api/auth/login" || echo 000)"
  if [ "${LOGIN_CODE}" = "200" ]; then
    break
  fi
  sleep 0.5
done

log "login response code: ${LOGIN_CODE}"
# Print only the first 200 bytes of the response body — we don't want to
# leak the JWT into the log file.
head -c 200 "${LOGIN_RESPONSE}" | sed 's/^/    /' | tee -a "${VERIFY_LOG}" >&2 || true
echo "" | tee -a "${VERIFY_LOG}" >&2

if [ "${LOGIN_CODE}" != "200" ]; then
  log "ERROR: admin login did not return 200 (got ${LOGIN_CODE})"
  rm -f "${LOGIN_RESPONSE}"
  exit 1
fi

# Sanity check: response should look like JSON with a `token` field.
if ! head -c 200 "${LOGIN_RESPONSE}" | grep -q '"token"'; then
  log "ERROR: response body did not contain a 'token' field"
  rm -f "${LOGIN_RESPONSE}"
  exit 1
fi
rm -f "${LOGIN_RESPONSE}"

log "login OK — backup is restorable"
# cleanup() will run on EXIT and stop the server + remove WORK_DIR.
exit 0
