#!/usr/bin/env bash
# ELEVA-303 phase 2 — rejoue l'intégralité des runs réels contre de vraies apps Next.js.
#
#   scripts/real-runs.sh            # tout : scaffold si absent, builds, serves, runs, teardown
#   scripts/real-runs.sh --runs     # saute create-next-app et les builds (apps déjà prêtes)
#
# Validation manuelle avant release — PAS un job de CI : il télécharge create-next-app
# et compile cinq builds. Les overlays d'app vivent dans tests/fixtures/next/ (committés,
# avec MATRIX.md qui explique chaque cas) ; tout le jetable — apps scaffoldées, builds,
# captures — atterrit dans tmp/real-runs/ (gitignoré), outputs/<scenario>.<mode>.txt,
# code de sortie en dernière ligne. Aucun serveur ne survit au script, y compris en cas
# d'échec ou d'interruption (trap EXIT).
set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$SCRIPT_DIR/.." && pwd)"
FIXTURES="$REPO/tests/fixtures/next"
HERE="$REPO/tmp/real-runs"
mkdir -p "$HERE"
BIN="$REPO/target/release/headless-seo-check"
OUT="$HERE/outputs"

PORT_MAIN=4311      # next start, build sain
PORT_POISON=4312    # next start, not-found.tsx empoisonné
PORT_EXPORT=4313    # python3 -m http.server sur out/
PORT_DEV=4314       # next dev — piège documenté, jamais une référence
PORT_BASEPATH=4315  # next start avec basePath: "/app"
PORT_CLOSED=4399    # personne n'écoute : exit code 2

PIDS=()

log() { printf '\n\033[1m==> %s\033[0m\n' "$*" >&2; }

cleanup() {
  local pid
  for pid in "${PIDS[@]:-}"; do
    [[ -n "${pid:-}" ]] || continue
    kill -TERM "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null
  done
  sleep 1
  for pid in "${PIDS[@]:-}"; do
    [[ -n "${pid:-}" ]] || continue
    kill -KILL "-$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null
  done
  # Ceinture et bretelles : le serveur Next fork un enfant qui ne meurt pas toujours
  # avec son parent. On tue par port, ce qui est la seule garantie réelle.
  local port
  for port in $PORT_MAIN $PORT_POISON $PORT_EXPORT $PORT_DEV $PORT_BASEPATH; do
    local victims
    victims="$(lsof -ti "tcp:$port" 2>/dev/null || true)"
    [[ -n "$victims" ]] && kill -KILL $victims 2>/dev/null
  done
  log "teardown terminé — ports libres :"
  lsof -i "tcp:$PORT_MAIN" -i "tcp:$PORT_POISON" -i "tcp:$PORT_EXPORT" -i "tcp:$PORT_DEV" -i "tcp:$PORT_BASEPATH" 2>/dev/null || echo "  (aucun)" >&2
}
trap cleanup EXIT INT TERM

wait_for() { # wait_for <port> <label>
  local port="$1" label="$2" i
  for i in $(seq 1 90); do
    if curl -fsS -o /dev/null "http://127.0.0.1:$port/" 2>/dev/null; then
      log "$label prêt sur :$port"
      return 0
    fi
    sleep 1
  done
  log "ERREUR : $label n'a jamais répondu sur :$port"
  return 1
}

serve_next() { # serve_next <dir> <port> <label>
  local dir="$1" port="$2" label="$3"
  ( cd "$dir" && exec bun run start -p "$port" ) >"$HERE/.$label.log" 2>&1 &
  PIDS+=("$!")
  wait_for "$port" "$label"
}

# run <scenario> <flags…>  — les trois modes, stdout+stderr+exit code capturés.
run() {
  local scenario="$1"; shift
  local mode label extra
  for mode in human plain json; do
    case "$mode" in
      human) extra=(--no-color) ;;
      plain) extra=(--plain) ;;
      json)  extra=(--json) ;;
    esac
    label="$OUT/$scenario.$mode.txt"
    {
      echo "\$ headless-seo-check $* ${extra[*]}"
      echo "--- stdout+stderr ---"
    } >"$label"
    "$BIN" "$@" "${extra[@]}" >>"$label" 2>&1
    echo "--- exit code: $? ---" >>"$label"
  done
  log "capturé : $scenario (human/plain/json)"
}

mkdir -p "$OUT"

# ---------------------------------------------------------------------------
# 0. binaire
# ---------------------------------------------------------------------------
log "cargo build --release"
( cd "$REPO" && cargo build --release ) || exit 1

if [[ "${1:-}" != "--runs" ]]; then
  # -------------------------------------------------------------------------
  # 1. scaffolds (idempotents)
  # -------------------------------------------------------------------------
  for app in app-main app-export; do
    if [[ ! -d "$HERE/$app/node_modules" ]]; then
      log "create-next-app $app"
      ( cd "$HERE" && bunx create-next-app@latest "$app" --ts --app --no-tailwind \
          --no-eslint --no-src-dir --no-turbopack --import-alias "@/*" --use-bun --yes ) || exit 1
      rm -f "$HERE/$app/app/page.module.css" "$HERE/$app/app/page.tsx" "$HERE/$app/app/layout.tsx"
    fi
    rsync -a "$FIXTURES/$app/" "$HERE/$app/"
  done

  # -------------------------------------------------------------------------
  # 2. builds de production (jamais next dev comme référence)
  # -------------------------------------------------------------------------
  log "build app-main (not-found.tsx SAIN)"
  cp "$FIXTURES/app-main/app/not-found.tsx" "$HERE/app-main/app/not-found.tsx"
  ( cd "$HERE/app-main" && bun run build ) >"$HERE/.build-sane.log" 2>&1 || { cat "$HERE/.build-sane.log"; exit 1; }
  rm -rf "$HERE/.next-sane" && cp -R "$HERE/app-main/.next" "$HERE/.next-sane"
  grep -E '^[┌├└] ' "$HERE/.build-sane.log" > "$OUT/build-tree.sane.txt"

  log "build app-main (not-found.tsx EMPOISONNÉ par await headers())"
  cp "$FIXTURES/not-found.poisoned.tsx" "$HERE/app-main/app/not-found.tsx"
  ( cd "$HERE/app-main" && bun run build ) >"$HERE/.build-poison.log" 2>&1 || { cat "$HERE/.build-poison.log"; exit 1; }
  rm -rf "$HERE/.next-poison" && cp -R "$HERE/app-main/.next" "$HERE/.next-poison"
  grep -E '^[┌├└] ' "$HERE/.build-poison.log" > "$OUT/build-tree.poisoned.txt"
  cp "$FIXTURES/app-main/app/not-found.tsx" "$HERE/app-main/app/not-found.tsx"

  diff -u "$OUT/build-tree.sane.txt" "$OUT/build-tree.poisoned.txt" \
    > "$OUT/build-tree.diff.txt" 2>&1 || true

  log "build app-main (basePath /app)"
  "$SCRIPT_DIR/with-basepath.py" "$HERE/app-main/next.config.ts" on
  ( cd "$HERE/app-main" && bun run build ) >"$HERE/.build-basepath.log" 2>&1 || { cat "$HERE/.build-basepath.log"; exit 1; }
  rm -rf "$HERE/.next-basepath" && cp -R "$HERE/app-main/.next" "$HERE/.next-basepath"
  cp "$FIXTURES/app-main/next.config.ts" "$HERE/app-main/next.config.ts"

  log "build app-export"
  ( cd "$HERE/app-export" && bun run build ) >"$HERE/.build-export.log" 2>&1 || { cat "$HERE/.build-export.log"; exit 1; }
fi

# ---------------------------------------------------------------------------
# 3. app-main SAINE — next start
# ---------------------------------------------------------------------------
rm -rf "$HERE/app-main/.next" && cp -R "$HERE/.next-sane" "$HERE/app-main/.next"
serve_next "$HERE/app-main" "$PORT_MAIN" "next-sane" || exit 1
MAIN="http://127.0.0.1:$PORT_MAIN"

curl -sS -D "$OUT/headers.sane.txt" -o /dev/null "$MAIN/static" -A "Googlebot" || true
for p in / /static /force-dynamic /isr /noindex-header /hreflang-good /gone /sitemap.xml /robots.txt; do
  { echo "=== $p"; curl -sS -D- -o /dev/null "$MAIN$p" -A "Googlebot" | tr -d '\r'; } >> "$OUT/headers.sane.txt"
done

run sane-all              "$MAIN" -a
run sane-sample3          "$MAIN" -s 3
run sane-verbose          "$MAIN" -a --verbose
run sane-quiet            "$MAIN" -a -q
run sane-allow-dynamic    "$MAIN" -a --allow-dynamic
run sane-no-verify        "$MAIN" -a --no-verify
run sane-healthy-only     "$MAIN" --urls /canonical-self,/canonical-other,/hreflang-good,/hreflang-html-only
run sane-render-modes     "$MAIN" --urls /static,/force-dynamic,/isr
run sane-indexability     "$MAIN" --urls /noindex-meta,/noindex-header,/noindex-conflict,/canonical-self,/canonical-other
run sane-hreflang         "$MAIN" --urls /hreflang-good,/hreflang-drift,/hreflang-dead,/hreflang-drift-dead,/hreflang-html-only
run sane-content          "$MAIN" --urls /shell,/cjk,/cjk-thin,/thin,/static
run sane-urls-absolute    "$MAIN" --urls "$MAIN/static,$MAIN/cjk"

# ---------------------------------------------------------------------------
# 4. app-main EMPOISONNÉE — le bug fondateur
# ---------------------------------------------------------------------------
rm -rf "$HERE/app-main/.next" && cp -R "$HERE/.next-poison" "$HERE/app-main/.next"
serve_next "$HERE/app-main" "$PORT_POISON" "next-poison" || exit 1
POISON="http://127.0.0.1:$PORT_POISON"

for p in / /static /isr; do
  { echo "=== $p"; curl -sS -D- -o /dev/null "$POISON$p" -A "Googlebot" | tr -d '\r'; } >> "$OUT/headers.poisoned.txt"
done

run poisoned-all          "$POISON" -a
run poisoned-render-modes "$POISON" --urls /static,/force-dynamic,/isr
run poisoned-verbose      "$POISON" --urls /static --verbose

# ---------------------------------------------------------------------------
# 5. output: "export" servi par un serveur statique bête
# ---------------------------------------------------------------------------
( cd "$HERE/app-export/out" && exec python3 -m http.server "$PORT_EXPORT" --bind 127.0.0.1 ) \
  >"$HERE/.export.log" 2>&1 &
PIDS+=("$!")
wait_for "$PORT_EXPORT" "static-export" || exit 1
EXPORT="http://127.0.0.1:$PORT_EXPORT"

{ echo "=== /"; curl -sS -D- -o /dev/null "$EXPORT/" -A "Googlebot" | tr -d '\r'; } > "$OUT/headers.export.txt"

run export-all            "$EXPORT" -a
run export-verbose        "$EXPORT" -a --verbose

# ---------------------------------------------------------------------------
# 6. pièges
# ---------------------------------------------------------------------------
# basePath : le sitemap et robots.txt vivent sous /app, pas a la racine.
if [[ -d "$HERE/.next-basepath" ]]; then
  cp "$FIXTURES/app-main/next.config.ts" "$HERE/app-main/next.config.ts"
  "$SCRIPT_DIR/with-basepath.py" "$HERE/app-main/next.config.ts" on
  rm -rf "$HERE/app-main/.next" && cp -R "$HERE/.next-basepath" "$HERE/app-main/.next"
  ( cd "$HERE/app-main" && exec bun run start -p "$PORT_BASEPATH" ) >"$HERE/.basepath.log" 2>&1 &
  PIDS+=("$!")
  BASE="http://127.0.0.1:$PORT_BASEPATH"
  for i in $(seq 1 90); do curl -fsS -o /dev/null "$BASE/app/" 2>/dev/null && break; sleep 1; done
  log "basePath pret sur :$PORT_BASEPATH"
  run trap-basepath-root "$BASE" -a
  run trap-basepath-path "$BASE/app" -a
  run trap-basepath-urls "$BASE" --urls /app/static,/app/cjk
  cp "$FIXTURES/app-main/next.config.ts" "$HERE/app-main/next.config.ts"
fi

run trap-closed-port      "http://127.0.0.1:$PORT_CLOSED" -a
run trap-bad-flags        "$MAIN" -a -s 20

# next dev : JAMAIS une référence. Capturé uniquement pour prouver le piège.
( cd "$HERE/app-main" && exec bun run dev -p "$PORT_DEV" ) >"$HERE/.dev.log" 2>&1 &
PIDS+=("$!")
if wait_for "$PORT_DEV" "next-dev (PIÈGE)"; then
  DEV="http://127.0.0.1:$PORT_DEV"
  curl -sS -o /dev/null "$DEV/static" -A "Googlebot" || true
  { echo "=== /static (next dev)"; curl -sS -D- -o /dev/null "$DEV/static" -A "Googlebot" | tr -d '\r'; } > "$OUT/headers.dev.txt"
  run trap-next-dev "$DEV" --urls /static,/isr,/cjk
fi

log "TERMINÉ — voir $OUT"
