#!/usr/bin/env bash
# Test suite for SX HTTP server app config handling.
# Starts the server with different __app-config values and verifies behavior.
set -euo pipefail

cd "$(dirname "$0")/.."
PROJECT_DIR="$(pwd)"
SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
BASE_PORT=8090
PASS=0
FAIL=0
ERRORS=""
SERVER_PID=""
CURRENT_PORT=""

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

TMP_DIR=$(mktemp -d)

cleanup() {
  if [ -n "${SERVER_PID:-}" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
    kill "$SERVER_PID" 2>/dev/null
    wait "$SERVER_PID" 2>/dev/null || true
  fi
  rm -rf "$TMP_DIR"
}
trap cleanup EXIT

start_server() {
  local port="$1"
  local extra_env="${2:-}"
  CURRENT_PORT="$port"
  # Kill anything on this port
  kill $(lsof -ti :"$port") 2>/dev/null || true
  sleep 1
  # Start server
  eval "$extra_env SX_PROJECT_DIR=$PROJECT_DIR $SERVER --http $port" \
    > "$TMP_DIR/stdout" 2> "$TMP_DIR/stderr" &
  SERVER_PID=$!
  # Wait for "Listening" message
  local tries=0
  while ! grep -q "Listening" "$TMP_DIR/stderr" 2>/dev/null; do
    sleep 1
    tries=$((tries + 1))
    if [ $tries -gt 120 ]; then
      echo "TIMEOUT waiting for server on port $port"
      cat "$TMP_DIR/stderr"
      return 1
    fi
    if ! kill -0 "$SERVER_PID" 2>/dev/null; then
      echo "Server died during startup on port $port"
      cat "$TMP_DIR/stderr"
      return 1
    fi
  done
  echo "  (server ready on port $port)"
}

stop_server() {
  if [ -n "${SERVER_PID:-}" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
    kill "$SERVER_PID" 2>/dev/null
    wait "$SERVER_PID" 2>/dev/null || true
    sleep 1
  fi
  SERVER_PID=""
}

url() { echo "http://localhost:$CURRENT_PORT$1"; }

assert_status() {
  local desc="$1" path="$2" expected="$3"
  shift 3
  local actual
  actual=$(curl -s -o /dev/null -w "%{http_code}" "$@" "$(url "$path")" 2>/dev/null)
  if [ "$actual" = "$expected" ]; then
    PASS=$((PASS + 1))
    echo "  PASS: $desc"
  else
    FAIL=$((FAIL + 1))
    ERRORS="$ERRORS\n  FAIL: $desc — expected $expected, got $actual"
    echo "  FAIL: $desc — expected $expected, got $actual"
  fi
}

assert_redirect() {
  local desc="$1" path="$2" expected_path="$3"
  local actual
  actual=$(curl -s -o /dev/null -w "%{redirect_url}" "$(url "$path")" 2>/dev/null)
  local expected="http://localhost:$CURRENT_PORT$expected_path"
  if [ "$actual" = "$expected" ]; then
    PASS=$((PASS + 1))
    echo "  PASS: $desc"
  else
    FAIL=$((FAIL + 1))
    ERRORS="$ERRORS\n  FAIL: $desc — expected → $expected, got → $actual"
    echo "  FAIL: $desc — expected → $expected_path, got → $actual"
  fi
}

assert_body_contains() {
  local desc="$1" path="$2" expected="$3"
  shift 3
  curl -s "$@" "$(url "$path")" > "$TMP_DIR/body" 2>/dev/null
  if grep -qF "$expected" "$TMP_DIR/body"; then
    PASS=$((PASS + 1))
    echo "  PASS: $desc"
  else
    FAIL=$((FAIL + 1))
    local size=$(wc -c < "$TMP_DIR/body")
    ERRORS="$ERRORS\n  FAIL: $desc — body ($size bytes) missing '$expected'"
    echo "  FAIL: $desc — body ($size bytes) missing '$expected'"
  fi
}

assert_stderr_contains() {
  local desc="$1" expected="$2"
  if grep -qF "$expected" "$TMP_DIR/stderr" 2>/dev/null; then
    PASS=$((PASS + 1))
    echo "  PASS: $desc"
  else
    FAIL=$((FAIL + 1))
    ERRORS="$ERRORS\n  FAIL: $desc — stderr missing '$expected'"
    echo "  FAIL: $desc — stderr missing '$expected'"
  fi
}

assert_stderr_not_contains() {
  local desc="$1" expected="$2"
  if grep -qF "$expected" "$TMP_DIR/stderr" 2>/dev/null; then
    FAIL=$((FAIL + 1))
    ERRORS="$ERRORS\n  FAIL: $desc — stderr should NOT contain '$expected'"
    echo "  FAIL: $desc — stderr should NOT contain '$expected'"
  else
    PASS=$((PASS + 1))
    echo "  PASS: $desc"
  fi
}

# ── Setup ────────────────────────────────────────────────────────────

if [ ! -x "$SERVER" ]; then
  echo "ERROR: Server binary not found at $SERVER"
  echo "Run: cd hosts/ocaml && eval \$(opam env) && dune build"
  exit 1
fi

echo "=== SX HTTP Server Config Tests ==="
echo ""

# ── Test Group 1: Default config (standard app-config.sx) ───────────

echo "── Group 1: Standard config ──"
start_server $((BASE_PORT + 1))

assert_stderr_contains "config loaded" "App config loaded: title=SX prefix=/sx/"
assert_stderr_contains "warmup ran 9 pages" "Pre-warmed 9 pages"
assert_stderr_not_contains "no stepper cookie refs" "sx-home-stepper"

assert_status "homepage 200" "/sx/" 200
assert_status "AJAX homepage 200" "/sx/" 200 -H "sx-request: true"
assert_status "geography 200" "/sx/(geography)" 200
assert_status "language 200" "/sx/(language)" 200
assert_status "applications 200" "/sx/(applications)" 200
assert_redirect "root → /sx/" "/" "/sx/"
assert_status "handler 200" "/sx/(api.spec-detail)?name=render-to-html" 200
assert_status "static 200" "/static/styles/tw.css" 200
assert_status "debug 200" "/sx/_debug/eval?expr=(%2B%201%202)" 200
assert_status "unknown 404" "/nope" 404

assert_body_contains "title is SX" "/sx/" "<title>SX</title>"
assert_body_contains "AJAX returns SX wire format" "/sx/" "sx-swap-oob" -H "sx-request: true"
assert_body_contains "debug eval result" "/sx/_debug/eval?expr=(%2B%201%202)" "3"

stop_server
echo ""

# ── Test Group 2: Custom title + minimal warmup ─────────────────────

echo "── Group 2: Custom title ──"
mkdir -p "$TMP_DIR/custom-sx"
cp -r "$PROJECT_DIR/sx/sx/"* "$TMP_DIR/custom-sx/"
cat > "$TMP_DIR/custom-sx/app-config.sx" << 'SXEOF'
(define __app-config
  {:title "My Custom App"
   :path-prefix "/sx/"
   :home-path "/sx/"
   :inner-layout "~layouts/doc"
   :outer-layout "~shared:layout/app-body"
   :shell "~shared:shell/sx-page-shell"
   :client-libs (list "tw-layout.sx" "tw-type.sx" "tw.sx")
   :css-files (list "basics.css" "tw.css")
   :batchable-helpers (list "highlight" "component-source")
   :handler-prefixes (list "handler:ex-" "handler:reactive-" "handler:")
   :warmup-paths (list "/sx/")
   :init-script :default})
SXEOF

start_server $((BASE_PORT + 2)) "SX_COMPONENTS_DIR=$TMP_DIR/custom-sx"

assert_stderr_contains "custom title in log" "App config loaded: title=My Custom App"
assert_stderr_contains "warmup 1 page" "Pre-warmed 1 pages"
assert_status "homepage works" "/sx/" 200
assert_body_contains "custom title in HTML" "/sx/" "<title>My Custom App</title>"

stop_server
echo ""

# ── Test Group 3: No config (missing __app-config) ──────────────────

echo "── Group 3: No app config (defaults) ──"
mkdir -p "$TMP_DIR/noconfig-sx"
cp -r "$PROJECT_DIR/sx/sx/"* "$TMP_DIR/noconfig-sx/"
rm -f "$TMP_DIR/noconfig-sx/app-config.sx"

start_server $((BASE_PORT + 3)) "SX_COMPONENTS_DIR=$TMP_DIR/noconfig-sx"

assert_stderr_contains "defaults fallback" "No __app-config found, using defaults"
assert_status "homepage without config" "/sx/" 200
assert_redirect "root redirect without config" "/" "/sx/"
assert_body_contains "default title" "/sx/" "<title>SX</title>"

stop_server
echo ""

# ── Test Group 4: Minimal config (only title) ───────────────────────

echo "── Group 4: Minimal config ──"
mkdir -p "$TMP_DIR/minimal-sx"
cp -r "$PROJECT_DIR/sx/sx/"* "$TMP_DIR/minimal-sx/"
cat > "$TMP_DIR/minimal-sx/app-config.sx" << 'SXEOF'
(define __app-config {:title "Bare"})
SXEOF

start_server $((BASE_PORT + 4)) "SX_COMPONENTS_DIR=$TMP_DIR/minimal-sx"

assert_stderr_contains "minimal config log" "App config loaded: title=Bare"
assert_status "homepage with minimal config" "/sx/" 200
assert_body_contains "bare title" "/sx/" "<title>Bare</title>"
# Defaults should still work for everything not specified
assert_redirect "default redirect" "/" "/sx/"
assert_status "default debug" "/sx/_debug/eval?expr=1" 200

stop_server
echo ""

# ── Summary ──────────────────────────────────────────────────────────

echo "=== Results: $PASS passed, $FAIL failed ==="
if [ $FAIL -gt 0 ]; then
  echo -e "\nFailures:$ERRORS"
  exit 1
else
  echo "All tests passed."
  exit 0
fi
