#!/usr/bin/env bash
#
# SpecVerse new-user smoke test.
#
# Unlike scripts/smoke.sh which tests the in-repo (potentially
# unreleased) code, this script simulates a brand-new user:
#
#   1. Assumes `spv` is already installed globally — caller is
#      responsible for `npm install -g @specverse/self@latest`
#      beforehand (devcontainer does this via postCreateCommand).
#   2. `spv init new-user-demo` (uses the implicit full-stack default)
#   3. Follows the init README: realize → install → db:setup → dev.
#   4. Hits the full-stack template's /api/greetings endpoint.
#   5. Asserts POST → GET → DELETE all work.
#
# This catches "we shipped packages to npm but the end-to-end init
# flow is broken" — a different class of bug from the in-repo
# smoke test. If smoke.sh passes but this fails, your repo is fine
# but a recent publish is broken.
#
# Usage:
#   ./scripts/smoke-new-user.sh
#   SMOKE_KEEP=1 ./scripts/smoke-new-user.sh    # keep tmp dir
#   SMOKE_DEBUG=1 ./scripts/smoke-new-user.sh   # verbose

set -euo pipefail

# ─────────────────────────────────────────────────────────────────────
# Config
# ─────────────────────────────────────────────────────────────────────

SMOKE_DIR="${SMOKE_DIR:-$(mktemp -d -t specverse-newuser-XXXXXX)}"
PROJECT_NAME="new-user-demo"
BACKEND_PID=""

# ─────────────────────────────────────────────────────────────────────
# Helpers
# ─────────────────────────────────────────────────────────────────────

log()   { printf '\033[1;34m[new-user]\033[0m %s\n' "$*"; }
pass()  { printf '\033[1;32m  ✓\033[0m %s\n' "$*"; }
fail()  { printf '\033[1;31m  ✗\033[0m %s\n' "$*" >&2; exit 1; }
debug() { [ -n "${SMOKE_DEBUG:-}" ] && printf '\033[2m  · %s\033[0m\n' "$*" || true; }

kill_tree() {
  local pid="$1"
  [ -z "$pid" ] && return 0
  local pids
  pids=$(pgrep -P "$pid" 2>/dev/null || true)
  for child in $pids; do kill_tree "$child"; done
  kill "$pid" 2>/dev/null || true
}

cleanup() {
  if [ -n "$BACKEND_PID" ]; then
    log "Stopping backend (pid $BACKEND_PID)"
    kill_tree "$BACKEND_PID"
    wait "$BACKEND_PID" 2>/dev/null || true
  fi
  if [ -z "${SMOKE_KEEP:-}" ] && [ -d "$SMOKE_DIR" ]; then
    rm -rf "$SMOKE_DIR"
  fi
}
trap cleanup EXIT INT TERM

pick_port() {
  python3 -c 'import socket; s=socket.socket(); s.bind(("",0)); print(s.getsockname()[1]); s.close()'
}

wait_for_http() {
  local url="$1"; local max_wait="${2:-30}"; local elapsed=0
  while [ "$elapsed" -lt "$max_wait" ]; do
    if curl -sf "$url" -o /dev/null 2>&1; then return 0; fi
    sleep 0.5; elapsed=$((elapsed + 1))
  done
  return 1
}

assert_http() {
  local method="$1" url="$2" expected="$3" body="${4:-}" label="$5"
  local args=(-sS -o /tmp/newuser-response.json -w '%{http_code}' -X "$method" "$url")
  [ -n "$body" ] && args+=(-H 'Content-Type: application/json' -d "$body")
  local status
  status=$(curl "${args[@]}" 2>/tmp/newuser-curl-err || true)
  if [ "$status" != "$expected" ]; then
    fail "$label: expected HTTP $expected, got $status — body: $(cat /tmp/newuser-response.json 2>/dev/null | head -c 500)"
  fi
  pass "$label ($status)"
}

# ─────────────────────────────────────────────────────────────────────
# Preflight
# ─────────────────────────────────────────────────────────────────────

log "New-user smoke test starting in $SMOKE_DIR"

if ! command -v spv >/dev/null 2>&1; then
  fail "\`spv\` not found on PATH. Install with: npm install -g @specverse/self@latest"
fi

INSTALLED_VERSION=$(spv --version 2>&1 | tail -1)
log "Using spv (@specverse/self) version: $INSTALLED_VERSION"

# ─────────────────────────────────────────────────────────────────────
# 1. spv init — like a brand-new user
# ─────────────────────────────────────────────────────────────────────

cd "$SMOKE_DIR"

log "Running: spv init $PROJECT_NAME"
spv init "$PROJECT_NAME"

if [ ! -d "$PROJECT_NAME" ]; then
  fail "spv init did not create $PROJECT_NAME directory"
fi
pass "Project created at $SMOKE_DIR/$PROJECT_NAME"

cd "$PROJECT_NAME"
debug "Project contents: $(ls -1 | tr '\n' ' ')"

# ─────────────────────────────────────────────────────────────────────
# 2. Realize (the new user follows the template README's steps)
# ─────────────────────────────────────────────────────────────────────

log "Realizing the spec"
spv realize all specs/main.specly -m manifests/implementation.yaml -o generated/code >/dev/null
pass "Realize complete"

# ─────────────────────────────────────────────────────────────────────
# 3. Install + Prisma setup
# ─────────────────────────────────────────────────────────────────────

cd generated/code

# Full-stack dev environment now declares SQLite in the manifest, so
# the generated prisma schema should already use sqlite. Keep the
# safety-net sed below in case a future template switches back to
# postgres — it's a no-op when the schema is already sqlite.
log "Installing dependencies"
npm install --silent --no-audit --no-fund >/dev/null 2>&1

# Force SQLite so this runs without a Postgres server
cd backend
if [ -f prisma/schema.prisma ] && grep -q 'provider = "postgresql"' prisma/schema.prisma; then
  log "Patching Prisma schema to SQLite for smoke test"
  sed -i.bak 's/provider = "postgresql"/provider = "sqlite"/' prisma/schema.prisma
  sed -i.bak 's|env("DATABASE_URL")|"file:./smoke.db"|' prisma/schema.prisma
  rm -f prisma/schema.prisma.bak
fi

log "Generating Prisma client"
npx --yes prisma generate >/dev/null 2>&1

log "Pushing Prisma schema to SQLite"
npx --yes prisma db push --skip-generate --accept-data-loss >/dev/null 2>&1

# ─────────────────────────────────────────────────────────────────────
# 4. Start the backend
# ─────────────────────────────────────────────────────────────────────

PORT=$(pick_port)
BASE_URL="http://127.0.0.1:$PORT"
log "Starting backend on port $PORT"
PORT="$PORT" npx tsx src/main.ts > "$SMOKE_DIR/backend.log" 2>&1 &
BACKEND_PID=$!

if ! wait_for_http "$BASE_URL/health" 30; then
  log "Backend failed to start — tail of backend.log:"
  tail -40 "$SMOKE_DIR/backend.log" >&2
  fail "Backend didn't become ready"
fi
pass "Backend responding on $BASE_URL"

# ─────────────────────────────────────────────────────────────────────
# 5. Hit the full-stack template's User endpoints
# ─────────────────────────────────────────────────────────────────────
#
# Full-stack ships the task-management demo (User, Project, Task).
# We smoke-test against User because it has no belongsTo
# relationships, so POST works without setting up related entities.

log "Running basic CRUD against /api/categories + /api/items (with FK)"

# 1. Create a Category first — Item's category FK needs a target.
assert_http POST "$BASE_URL/api/categories" 201 \
  '{"name":"smoke-cat","description":"smoke test category"}' \
  "POST /api/categories (create)"

CAT_ID=$(python3 -c 'import json; print(json.load(open("/tmp/newuser-response.json"))["id"])')
debug "Created category id=$CAT_ID"

# 2. Create an Item with a belongsTo FK.
assert_http POST "$BASE_URL/api/items" 201 \
  "{\"name\":\"smoke-item\",\"description\":\"smoke test item\",\"status\":\"active\",\"categoryId\":\"$CAT_ID\"}" \
  "POST /api/items (create with FK)"

ITEM_ID=$(python3 -c 'import json; print(json.load(open("/tmp/newuser-response.json"))["id"])')
debug "Created item id=$ITEM_ID"

assert_http GET "$BASE_URL/api/items/$ITEM_ID" 200 "" \
  "GET /api/items/:id (retrieve)"

assert_http GET "$BASE_URL/api/items" 200 "" \
  "GET /api/items (list)"

# ─────────────────────────────────────────────────────────────────────
# Done
# ─────────────────────────────────────────────────────────────────────

log "New-user smoke test passed ✅"
log "Published packages work for a brand-new project."
