#!/bin/bash
# gate-f0.sh — the F0 (foundation) gate.
#
#   main/scripts/gate-f0.sh [base-url]
#
# A milestone is done when its gate exits 0, never when it looks done. This
# asserts the foundation's load-bearing properties against a RUNNING deployment:
# the version is live, the public surface is public, the gated surface is
# closed, admission is invite-only, and the database has its schema.
#
# Default target is the Docker-bridge address so it can run on the host without
# depending on DNS, Caddy or a certificate. Pass https://dailysplice.com to
# check the same properties through the real front door.

set -uo pipefail

BASE="${1:-http://172.17.0.1:3141}"
APP_CONTAINER="dailysplice-app"
DB_CONTAINER="dailysplice-db"
VERSION_FILE="$(dirname "$0")/../VERSION"

pass=0
fail=0

check() { # check <description> <expected> <actual>
  if [ "$2" = "$3" ]; then
    printf '  ok    %s\n' "$1"
    pass=$((pass + 1))
  else
    printf '  FAIL  %s (expected %s, got %s)\n' "$1" "$2" "$3"
    fail=$((fail + 1))
  fi
}

status() { curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$@"; }
body() { curl -s --max-time 10 "$@"; }
json() { python3 -c "import sys,json;print(json.load(sys.stdin).get('$1'))"; }

echo "F0 gate — target $BASE"

echo "public surface"
check "GET / serves the SPA" 200 "$(status "$BASE/")"
check "HEAD / is answered" 200 "$(status -I "$BASE/")"
check "/api/health is public" 200 "$(status "$BASE/api/health")"
check "/api/me is public" 200 "$(status "$BASE/api/me")"
check "/api/me reports anonymous" "False" "$(body "$BASE/api/me" | json authenticated)"

echo "the gate is closed by default"
check "/api/account requires a session" 401 "$(status "$BASE/api/account")"
check "/api/version requires a session" 401 "$(status "$BASE/api/version")"
check "/api/admin/invites requires a session" 401 "$(status "$BASE/api/admin/invites")"
check "/api/openapi.json requires a session" 401 "$(status "$BASE/api/openapi.json")"

echo "spotify routes are gated"
check "/api/spotify/status requires a session" 401 "$(status "$BASE/api/spotify/status")"
check "/api/spotify/disconnect requires a session" 401 \
  "$(status -X POST "$BASE/api/spotify/disconnect")"
# A gated BROWSER path must redirect, not answer a navigation with JSON.
check "/spotify/connect redirects when signed out" 302 "$(status "$BASE/spotify/connect")"
check "/spotify/callback redirects when signed out" 302 \
  "$(status "$BASE/spotify/callback?code=x&state=y")"

echo "sign-in is wired"
check "auth is configured" "True" "$(body "$BASE/api/me" | json auth_available)"
check "/login starts the OAuth flow" 302 "$(status "$BASE/login")"
LOGIN_TARGET=$(curl -s -o /dev/null -w '%{redirect_url}' --max-time 10 "$BASE/login")
case "$LOGIN_TARGET" in
  https://auth.bowden.works/app-authorize?*code_challenge_method=S256*)
    echo "  ok    /login uses PKCE against auth.bowden.works"; pass=$((pass + 1)) ;;
  *)
    echo "  FAIL  /login target unexpected: $LOGIN_TARGET"; fail=$((fail + 1)) ;;
esac

echo "the deployed version matches the source"
EXPECTED_VERSION=$(tr -d '[:space:]' < "$VERSION_FILE")
RUNNING_VERSION=$(docker exec "$APP_CONTAINER" cat VERSION 2>/dev/null | tr -d '[:space:]')
check "container VERSION == main/VERSION" "$EXPECTED_VERSION" "$RUNNING_VERSION"
BUNDLE_VERSION=$(body "$BASE/" >/dev/null; docker exec "$APP_CONTAINER" \
  sh -c "grep -ho '\"$EXPECTED_VERSION\"' static/assets/*.js | head -1" 2>/dev/null | tr -d '"')
check "built bundle carries the version" "$EXPECTED_VERSION" "$BUNDLE_VERSION"

echo "database"
TABLES=$(docker exec "$DB_CONTAINER" psql -U dailysplice -d dailysplice -tAc \
  "SELECT count(*) FROM information_schema.tables
    WHERE table_schema='public' AND table_name IN ('users','invites')" 2>/dev/null)
check "users + invites exist" 2 "$TABLES"
SEEDED=$(docker exec "$DB_CONTAINER" psql -U dailysplice -d dailysplice -tAc \
  "SELECT count(*) FROM invites WHERE role='superadmin'" 2>/dev/null)
check "a superadmin invite is seeded" 1 "$SEEDED"
SPOTIFY_TABLE=$(docker exec "$DB_CONTAINER" psql -U dailysplice -d dailysplice -tAc \
  "SELECT count(*) FROM information_schema.tables
    WHERE table_schema='public' AND table_name='spotify_accounts'" 2>/dev/null)
check "spotify_accounts exists" 1 "$SPOTIFY_TABLE"
# No stored token may ever be readable. If a row exists, its token columns must
# be Fernet ciphertext, which always begins "gAAAAA".
PLAINTEXT=$(docker exec "$DB_CONTAINER" psql -U dailysplice -d dailysplice -tAc \
  "SELECT count(*) FROM spotify_accounts
    WHERE access_token_encrypted NOT LIKE 'gAAAAA%'
       OR refresh_token_encrypted NOT LIKE 'gAAAAA%'" 2>/dev/null)
check "no unencrypted spotify tokens stored" 0 "$PLAINTEXT"

echo "the host must never be gated (a gate would 404 sign-in)"
# grep -c prints "0" AND exits 1 when there is no match, so `|| echo 0` would
# append a SECOND zero. Swallow the exit status instead, and default to 0 for
# the case where the conf file is unreadable from this account.
GATED=$(grep -c 'import id-auth' /srv/caddy/sites.d/dailysplice.com.conf 2>/dev/null || true)
check "no id-auth gate on the host" 0 "${GATED:-0}"

echo
echo "$pass passed, $fail failed"
[ "$fail" -eq 0 ] || exit 1
