#!/usr/bin/env bash
# Prod post-deploy smoke checks (M24-10): is production up and serving after a deploy, using ONLY
# public, unauthenticated signals? Production cannot run the authenticated e2e suite - test-login
# is structurally forbidden there (config.py::_forbid_test_login_in_production) - so this script
# is prod's whole post-deploy verdict:
#
#   1. GET <api>/health           -> 200 with the version + engine readiness fields
#   2. GET <base>/api/auth/config -> 200 with testLogin:false - the SECURITY REGRESSION GUARD:
#      a build that ships prod with the test-login bypass reachable must fail loudly here
#   3. GET <base>/                -> 200 with the login page shell (SPA mount + title)
#   4. every /assets/*.js|css the shell references -> 200 (the login page's render preconditions)
#
# Every check runs (no fail-fast) so one red run shows the whole picture; the verdict lands in
# the Actions step summary and the exit code. On failure the summary points at the MANUAL
# rollback path: prod deploys are deliberate, manual dispatches with a human already at the
# wheel, prod keeps no "last e2e-green" bookkeeping (scripts/deploy/e2e-promote-rollback.sh
# rejects prod by design), and an unauthenticated smoke is too shallow a signal to auto-revert
# production on - so rollback stays a human decision (docs/deployment.md, "Prod post-deploy
# smoke").
#
# Contract from the workflow (smoke-prod.yml), via the environment:
#   SMOKE_BASE_URL     - the deployed prod dashboard origin (required)
#   SMOKE_API_BASE_URL - the prod API origin for /health; defaults to SMOKE_BASE_URL. Needed
#                        while prod web is Cloudflare Pages: its proxy (apps/web/functions)
#                        forwards /api/* only, and /health is not under /api. A single-origin
#                        prod (an ingress routing /health, like dev's k3s ingress) leaves this
#                        unset.
#
# No credentials, no secrets: everything probed here is public surface. Offline tests (stubbed
# curl): scripts/deploy/tests/test_smoke_prod.sh.
set -euo pipefail

base="${SMOKE_BASE_URL:?SMOKE_BASE_URL not set (the deployed prod dashboard origin)}"
base="${base%/}"
api_base="${SMOKE_API_BASE_URL:-$base}"
api_base="${api_base%/}"

workdir="$(mktemp -d)"
trap 'rm -rf "$workdir"' EXIT

failures=0
rows=()
ok() { # <description>
  echo "  ok: $1"
  rows+=("| ✅ | $1 |")
}
bad() { # <description>
  echo "::error::smoke (prod): $1" >&2
  rows+=("| ❌ | $1 |")
  failures=$((failures + 1))
}
fetch() { # <url> <outfile> -> echoes the HTTP status code ('000' on a transport failure)
  curl -sS --max-time 30 -o "$2" -w '%{http_code}' "$1" || true
}

echo "==> smoke (prod): dashboard=$base api=$api_base"

# --- 1) API health: 200 + the version/engine readiness fields the endpoint contracts ---
code="$(fetch "$api_base/health" "$workdir/health.json")"
if [ "$code" = "200" ]; then
  ok "GET /health returned 200"
else
  bad "GET /health returned HTTP $code (expected 200)"
fi
if jq -er '.version | strings | select(length > 0)' "$workdir/health.json" >/dev/null 2>&1; then
  ok "/health reports a version ($(jq -r '.version' "$workdir/health.json"))"
else
  bad "/health is missing the version field (body not the API's health contract?)"
fi
if jq -er '.engine.mode | strings | select(length > 0)' "$workdir/health.json" >/dev/null 2>&1; then
  ok "/health reports the engine readiness ($(jq -r \
    '"mode=" + .engine.mode + " ready=" + (.engine.ready | tostring)' "$workdir/health.json"))"
else
  bad "/health is missing the engine readiness fields"
fi

# --- 2) Security regression guard: the test-login bypass must NOT be reachable on prod ---
code="$(fetch "$base/api/auth/config" "$workdir/auth.json")"
if [ "$code" = "200" ]; then
  ok "GET /api/auth/config returned 200"
else
  bad "GET /api/auth/config returned HTTP $code (expected 200)"
fi
# Fails closed: absent key, non-JSON body, or testLogin:true all trip the guard.
if jq -e '.testLogin == false' "$workdir/auth.json" >/dev/null 2>&1; then
  ok "security guard: testLogin is false on prod"
else
  bad "SECURITY REGRESSION: /api/auth/config does not report testLogin:false - a prod build may \
have shipped with the test-login bypass reachable"
fi

# --- 3) The login page renders: the SPA shell with its mount + title ---
code="$(fetch "$base/" "$workdir/shell.html")"
if [ "$code" = "200" ]; then
  ok "GET / (login page shell) returned 200"
else
  bad "GET / (login page shell) returned HTTP $code (expected 200)"
fi
if grep -q 'id="root"' "$workdir/shell.html"; then
  ok 'login page shell contains the SPA mount (<div id="root">)'
else
  bad 'login page shell is missing the SPA mount (<div id="root">)'
fi
if grep -q '<title>Bloom</title>' "$workdir/shell.html"; then
  ok "login page shell carries the Bloom title"
else
  bad "login page shell is missing the Bloom title"
fi

# --- 4) Core static assets: every bundle the shell references must load ---
mapfile -t assets < <(grep -oE '"/assets/[^"]+"' "$workdir/shell.html" | tr -d '"' | sort -u)
js_refs=0
for asset in "${assets[@]:-}"; do
  case "$asset" in *.js) js_refs=$((js_refs + 1)) ;; esac
done
if [ "$js_refs" -ge 1 ]; then
  ok "login page shell references ${#assets[@]} built asset(s), including a JS entry bundle"
else
  bad "login page shell references no built /assets/*.js bundle - not a production build?"
fi
for asset in "${assets[@]:-}"; do
  [ -n "$asset" ] || continue
  code="$(fetch "$base$asset" "$workdir/asset.bin")"
  if [ "$code" = "200" ] && [ -s "$workdir/asset.bin" ]; then
    ok "asset $asset returned 200"
  else
    bad "asset $asset returned HTTP $code$([ -s "$workdir/asset.bin" ] || echo ' (empty body)')"
  fi
done

# --- Verdict: step summary is the notification, the exit code reds the run ---
summary() { [ -z "${GITHUB_STEP_SUMMARY:-}" ] || echo "$1" >> "$GITHUB_STEP_SUMMARY"; }
summary "## Smoke (prod): $base"
summary ""
summary "| | check |"
summary "| --- | --- |"
for row in "${rows[@]}"; do summary "$row"; done
summary ""

if [ "$failures" -gt 0 ]; then
  summary "### ❌ $failures smoke check(s) failed"
  summary ""
  summary "Prod rollback is deliberately **manual** (a human dispatched this deploy; see"
  summary "docs/deployment.md, \"Prod post-deploy smoke\"): run **Deploy (production)** with"
  summary "\`mode: rollback\` to restore the previous API image, and restore the previous web"
  summary "deployment from the Cloudflare Pages deployment list (or re-run"
  summary "**Deploy Web (production)** from the last good commit)."
  echo "::error::smoke (prod): $failures check(s) failed - see the step summary" >&2
  exit 1
fi
summary "All checks passed."
echo "==> smoke (prod): all checks passed"
