#!/usr/bin/env bash
# Phase 12 — End-to-end validation for embedded-claude-terminal.
#
# Boots a TEST uvicorn on TEST_PORT (default 18000) so we don't collide with
# any production server (8431). Runs every spec §12 checklist item against
# the live process, sums pass/fail, writes a human-readable report next to
# validate.sh, then tears the test server down and verifies process-group
# cleanup completed.
#
# Exits 0 only if every checklist item passes.

set -euo pipefail

# ── paths ────────────────────────────────────────────────────────────────
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
REPO_ROOT="$( cd "${SCRIPT_DIR}/.." >/dev/null 2>&1 && pwd )"
PROJECT_ROOT="$( cd "${REPO_ROOT}/.." >/dev/null 2>&1 && pwd )"
SPEC_DIR="${PROJECT_ROOT}/consultations/recoil/embedded-claude-terminal"
REPORT_FILE="${SPEC_DIR}/PHASE_12_REPORT.md"

TEST_PORT="${TEST_PORT:-18000}"
BASE_URL="http://localhost:${TEST_PORT}"

UVICORN_LOG="$(mktemp -t p12_uvicorn.XXXXXX)"
UVICORN_PID_FILE="$(mktemp -t p12_uvicorn_pid.XXXXXX)"
SSE_OUT="$(mktemp -t p12_sse.XXXXXX)"
SSE_PID_FILE="$(mktemp -t p12_sse_pid.XXXXXX)"

cleanup() {
  set +e
  if [[ -s "${UVICORN_PID_FILE}" ]]; then
    local upid
    upid="$(cat "${UVICORN_PID_FILE}")"
    if kill -0 "${upid}" 2>/dev/null; then
      kill -TERM "${upid}" 2>/dev/null
      for _ in 1 2 3 4 5 6 7 8 9 10; do
        if ! kill -0 "${upid}" 2>/dev/null; then break; fi
        sleep 0.3
      done
      kill -KILL "${upid}" 2>/dev/null || true
    fi
  fi
  if [[ -s "${SSE_PID_FILE}" ]]; then
    local spid
    spid="$(cat "${SSE_PID_FILE}")"
    kill -TERM "${spid}" 2>/dev/null || true
  fi
  rm -f "${UVICORN_PID_FILE}" "${SSE_PID_FILE}"
  set -e
}
trap cleanup EXIT

# ── reporting helpers ────────────────────────────────────────────────────
PASS=0
FAIL=0
declare -a CHECK_LABELS
declare -a CHECK_RESULTS  # "PASS" or "FAIL: <reason>"

record_pass() {
  CHECK_LABELS+=("$1")
  CHECK_RESULTS+=("PASS")
  PASS=$((PASS + 1))
  echo "  [PASS] $1"
}
record_fail() {
  CHECK_LABELS+=("$1")
  CHECK_RESULTS+=("FAIL: $2")
  FAIL=$((FAIL + 1))
  echo "  [FAIL] $1 — $2"
}

# ── boot test uvicorn ────────────────────────────────────────────────────
echo "[phase-12] Booting test uvicorn on port ${TEST_PORT}…"
cd "${PROJECT_ROOT}"
(
  uvicorn recoil.api.main:app --host 127.0.0.1 --port "${TEST_PORT}" --log-level warning \
    > "${UVICORN_LOG}" 2>&1 &
  echo $! > "${UVICORN_PID_FILE}"
) </dev/null

# Wait up to 15s for /api/health to respond.
HEALTH_READY=0
for i in $(seq 1 30); do
  if curl -sf -o /dev/null "${BASE_URL}/api/health"; then
    HEALTH_READY=1
    break
  fi
  sleep 0.5
done

LABEL_BOOT="uvicorn recoil.api.main:app starts cleanly"
if [[ "${HEALTH_READY}" -eq 1 ]]; then
  record_pass "${LABEL_BOOT}"
else
  record_fail "${LABEL_BOOT}" "uvicorn /api/health did not respond within 15s (log: ${UVICORN_LOG})"
  # Continue anyway so we record consistent FAIL entries for downstream items.
fi

# ── helper: post-only-if-up ──────────────────────────────────────────────
api_alive() { [[ "${HEALTH_READY}" -eq 1 ]]; }

# ── 2. POST /api/ttyd/start returns a port ───────────────────────────────
LABEL_TTYD_START='POST /api/ttyd/start for "driver-beware" returns a port'
TTYD_PORT=""
if api_alive; then
  START_BODY="$(curl -sS -X POST "${BASE_URL}/api/ttyd/start" \
    -H 'content-type: application/json' \
    -d '{"project_id":"driver-beware"}' 2>/dev/null || true)"
  TTYD_PORT="$(printf '%s' "${START_BODY}" | python3 -c 'import sys,json;
try:
    d=json.load(sys.stdin)
    p=d.get("port")
    print(p if isinstance(p,int) else "")
except Exception:
    print("")
' 2>/dev/null || true)"
  if [[ -n "${TTYD_PORT}" ]]; then
    record_pass "${LABEL_TTYD_START}"
  else
    record_fail "${LABEL_TTYD_START}" "no integer 'port' in response: ${START_BODY}"
  fi
else
  record_fail "${LABEL_TTYD_START}" "uvicorn not up"
fi

# ── 3. ttyd serves HTML containing the keystroke-bubbler script ─────────
LABEL_TTYD_HTML="http://localhost:<port> serves the ttyd HTML and the keystroke bubbler script is present"
if [[ -n "${TTYD_PORT}" ]]; then
  # Give ttyd a moment to bind.
  TTYD_HTML=""
  for i in $(seq 1 20); do
    TTYD_HTML="$(curl -sS -m 2 "http://localhost:${TTYD_PORT}/" 2>/dev/null || true)"
    if printf '%s' "${TTYD_HTML}" | grep -q 'keystroke-bubbler'; then
      break
    fi
    sleep 0.3
  done
  if printf '%s' "${TTYD_HTML}" | grep -q 'keystroke-bubbler'; then
    record_pass "${LABEL_TTYD_HTML}"
  else
    record_fail "${LABEL_TTYD_HTML}" "GET http://localhost:${TTYD_PORT}/ did not contain 'keystroke-bubbler'"
  fi
else
  record_fail "${LABEL_TTYD_HTML}" "no ttyd port from /ttyd/start"
fi

# ── 4. SSE bridge: bus emit surfaces on /api/events/stream ──────────────
LABEL_SSE="POST /api/internal/bus emits a test event that surfaces on /api/events/stream"
if api_alive; then
  : > "${SSE_OUT}"
  (
    curl -N -sS --max-time 5 \
      "${BASE_URL}/api/events/stream?lastEventId=0" \
      > "${SSE_OUT}" 2>/dev/null &
    echo $! > "${SSE_PID_FILE}"
  ) </dev/null
  sleep 1
  curl -sS -X POST "${BASE_URL}/api/internal/bus" \
    -H 'content-type: application/json' \
    -d '{"scope":"phase12/test","severity":"info","summary":"e2e","payload":{"phase":12}}' \
    > /dev/null 2>&1 || true
  # Give the SSE generator time to flush.
  for i in 1 2 3 4 5 6 7 8 9 10; do
    if grep -q 'phase12/test' "${SSE_OUT}" 2>/dev/null; then
      break
    fi
    sleep 0.4
  done
  # Stop the SSE listener.
  if [[ -s "${SSE_PID_FILE}" ]]; then
    kill -TERM "$(cat "${SSE_PID_FILE}")" 2>/dev/null || true
    : > "${SSE_PID_FILE}"
  fi
  if grep -q 'phase12/test' "${SSE_OUT}" 2>/dev/null; then
    record_pass "${LABEL_SSE}"
  else
    SSE_PREVIEW="$(head -c 400 "${SSE_OUT}" 2>/dev/null | tr '\n' ' ')"
    record_fail "${LABEL_SSE}" "phase12/test not seen on SSE stream (got: ${SSE_PREVIEW:-<empty>})"
  fi
else
  record_fail "${LABEL_SSE}" "uvicorn not up"
fi

# ── 5. GET /api/chat/proposals/driver-beware returns [] (or list) ───────
LABEL_PROPS="GET /api/chat/proposals/driver-beware returns [] (or a list)"
if api_alive; then
  PROPS_HTTP="$(curl -sS -o /tmp/p12_props.json -w '%{http_code}' "${BASE_URL}/api/chat/proposals/driver-beware" 2>/dev/null || echo "000")"
  if [[ "${PROPS_HTTP}" == "200" ]] && python3 -c 'import json,sys; d=json.load(open("/tmp/p12_props.json")); sys.exit(0 if isinstance(d,list) else 1)' 2>/dev/null; then
    record_pass "${LABEL_PROPS}"
  else
    BODY="$(head -c 200 /tmp/p12_props.json 2>/dev/null || true)"
    record_fail "${LABEL_PROPS}" "http=${PROPS_HTTP} body=${BODY}"
  fi
else
  record_fail "${LABEL_PROPS}" "uvicorn not up"
fi

# ── 6. GET /api/clicks?project_id=driver-beware returns [] (or list) ────
LABEL_CLICKS="GET /api/clicks?project_id=driver-beware returns [] (or a list)"
if api_alive; then
  CLICKS_HTTP="$(curl -sS -o /tmp/p12_clicks.json -w '%{http_code}' "${BASE_URL}/api/clicks?project_id=driver-beware" 2>/dev/null || echo "000")"
  if [[ "${CLICKS_HTTP}" == "200" ]] && python3 -c 'import json,sys; d=json.load(open("/tmp/p12_clicks.json")); sys.exit(0 if isinstance(d,list) else 1)' 2>/dev/null; then
    record_pass "${LABEL_CLICKS}"
  else
    BODY="$(head -c 200 /tmp/p12_clicks.json 2>/dev/null || true)"
    record_fail "${LABEL_CLICKS}" "http=${CLICKS_HTTP} body=${BODY}"
  fi
else
  record_fail "${LABEL_CLICKS}" "uvicorn not up"
fi

# ── 7. GET /api/ttyd/context-window returns {used, limit, pct} ──────────
LABEL_CTX="GET /api/ttyd/context-window returns {used, limit, pct} (used/pct may be null)"
if api_alive; then
  CTX_HTTP="$(curl -sS -o /tmp/p12_ctx.json -w '%{http_code}' "${BASE_URL}/api/ttyd/context-window?project_id=driver-beware" 2>/dev/null || echo "000")"
  CTX_OK=0
  if [[ "${CTX_HTTP}" == "200" ]]; then
    if python3 -c '
import json,sys
d=json.load(open("/tmp/p12_ctx.json"))
need={"used","limit","pct"}
sys.exit(0 if isinstance(d,dict) and need.issubset(d.keys()) else 1)
' 2>/dev/null; then
      CTX_OK=1
    fi
  fi
  if [[ "${CTX_OK}" -eq 1 ]]; then
    record_pass "${LABEL_CTX}"
  else
    BODY="$(head -c 200 /tmp/p12_ctx.json 2>/dev/null || true)"
    record_fail "${LABEL_CTX}" "http=${CTX_HTTP} body=${BODY}"
  fi
else
  record_fail "${LABEL_CTX}" "uvicorn not up"
fi

# ── 8. POST /api/chat returns 404 (Phase 11 cleanup) ────────────────────
LABEL_CHAT="POST /api/chat returns 404 (Phase 11 cleanup confirmed)"
if api_alive; then
  CHAT_HTTP="$(curl -sS -o /dev/null -w '%{http_code}' \
    -X POST "${BASE_URL}/api/chat" \
    -H 'content-type: application/json' \
    -d '{"thread_id":"t","messages":[]}' 2>/dev/null || echo "000")"
  if [[ "${CHAT_HTTP}" == "404" ]]; then
    record_pass "${LABEL_CHAT}"
  else
    record_fail "${LABEL_CHAT}" "expected 404, got ${CHAT_HTTP}"
  fi
else
  record_fail "${LABEL_CHAT}" "uvicorn not up"
fi

# ── 9. POST /api/ttyd/stop kills the process group ──────────────────────
LABEL_TTYD_STOP="POST /api/ttyd/stop kills the process group; pgrep ttyd returns nothing"
if api_alive; then
  curl -sS -X POST "${BASE_URL}/api/ttyd/stop" \
    -H 'content-type: application/json' \
    -d '{"project_id":"driver-beware"}' > /dev/null 2>&1 || true
  # Wait up to ~5s for ttyd process group to disappear.
  TTYD_GONE=0
  for i in $(seq 1 25); do
    if ! pgrep -x ttyd > /dev/null 2>&1; then
      TTYD_GONE=1
      break
    fi
    sleep 0.2
  done
  if [[ "${TTYD_GONE}" -eq 1 ]]; then
    record_pass "${LABEL_TTYD_STOP}"
  else
    PIDS="$(pgrep -x ttyd | tr '\n' ' ' || true)"
    record_fail "${LABEL_TTYD_STOP}" "pgrep ttyd still returns: ${PIDS}"
  fi
else
  record_fail "${LABEL_TTYD_STOP}" "uvicorn not up"
fi

# ── 10. Frontend tsc clean ──────────────────────────────────────────────
# Spec §12 wording: "cd recoil/console-v2 && pnpm tsc --noEmit succeeds".
# console-v2 is a pnpm workspace with no root tsconfig.json; the canonical
# project-wide typecheck is `pnpm typecheck` (which fans out via `pnpm -r
# typecheck` to each package's own tsc --noEmit). Same intent — every
# package's TypeScript compiles cleanly with no emit.
LABEL_TSC="cd recoil/console-v2 && pnpm typecheck succeeds (workspace-wide tsc --noEmit)"
TSC_LOG="$(mktemp -t p12_tsc.XXXXXX)"
if (cd "${REPO_ROOT}/console-v2" && pnpm typecheck) > "${TSC_LOG}" 2>&1; then
  record_pass "${LABEL_TSC}"
else
  TAIL="$(tail -n 20 "${TSC_LOG}" | tr '\n' ' ')"
  record_fail "${LABEL_TSC}" "typecheck failed (tail: ${TAIL})"
fi

# ── 11. ADR files 0006-0010 exist ───────────────────────────────────────
LABEL_ADR="All ADR files exist (recoil/docs/adr/0006 through 0010)"
ADR_DIR="${REPO_ROOT}/docs/adr"
ADR_MISSING=()
for n in 0006 0007 0008 0009 0010; do
  matches=( "${ADR_DIR}"/"${n}"-*.md )
  if [[ ! -f "${matches[0]}" ]]; then
    ADR_MISSING+=("${n}")
  fi
done
if [[ "${#ADR_MISSING[@]}" -eq 0 ]]; then
  record_pass "${LABEL_ADR}"
else
  record_fail "${LABEL_ADR}" "missing: ${ADR_MISSING[*]}"
fi

# ── 12. recoil/CONTEXT.md exists ────────────────────────────────────────
LABEL_CTX_DOC="recoil/CONTEXT.md exists"
if [[ -f "${REPO_ROOT}/CONTEXT.md" ]]; then
  record_pass "${LABEL_CTX_DOC}"
else
  record_fail "${LABEL_CTX_DOC}" "${REPO_ROOT}/CONTEXT.md not found"
fi

# ── tear down uvicorn, then verify ttyd stays gone after atexit ─────────
echo "[phase-12] Stopping test uvicorn…"
if [[ -s "${UVICORN_PID_FILE}" ]]; then
  UPID="$(cat "${UVICORN_PID_FILE}")"
  if kill -0 "${UPID}" 2>/dev/null; then
    kill -TERM "${UPID}" 2>/dev/null || true
    for _ in 1 2 3 4 5 6 7 8 9 10; do
      if ! kill -0 "${UPID}" 2>/dev/null; then break; fi
      sleep 0.3
    done
    kill -KILL "${UPID}" 2>/dev/null || true
  fi
  : > "${UVICORN_PID_FILE}"
fi
sleep 1

# Bonus: confirm pgrep ttyd shows nothing post-shutdown (atexit cleanup).
if pgrep -x ttyd > /dev/null 2>&1; then
  echo "[phase-12] WARNING: ttyd still running after uvicorn shutdown:"
  pgrep -lx ttyd || true
fi

# ── write report ────────────────────────────────────────────────────────
TOTAL=$((PASS + FAIL))
if [[ "${FAIL}" -eq 0 ]]; then
  STATUS="PASS"
else
  STATUS="FAIL"
fi
TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"

{
  echo "# Phase 12 — End-to-End Validation Report"
  echo
  echo "- Generated: ${TIMESTAMP}"
  echo "- Test port: ${TEST_PORT}"
  echo "- Result: **${STATUS}** (${PASS}/${TOTAL} checks passed)"
  echo
  echo "## Checklist"
  echo
  for i in "${!CHECK_LABELS[@]}"; do
    label="${CHECK_LABELS[$i]}"
    result="${CHECK_RESULTS[$i]}"
    if [[ "${result}" == "PASS" ]]; then
      echo "- [x] ${label}"
    else
      echo "- [ ] ${label} (${result})"
    fi
  done
  echo
  if [[ "${FAIL}" -gt 0 ]]; then
    echo "## Failure Summary"
    echo
    for i in "${!CHECK_LABELS[@]}"; do
      label="${CHECK_LABELS[$i]}"
      result="${CHECK_RESULTS[$i]}"
      if [[ "${result}" != "PASS" ]]; then
        echo "- ${label}: ${result#FAIL: }"
      fi
    done
    echo
  fi
  echo "## Logs"
  echo
  echo "- uvicorn log: \`${UVICORN_LOG}\`"
  echo "- SSE capture: \`${SSE_OUT}\`"
} > "${REPORT_FILE}"

echo "[phase-12] Wrote report → ${REPORT_FILE}"
echo "[phase-12] Result: ${STATUS} (${PASS}/${TOTAL} passed)"

if [[ "${FAIL}" -eq 0 ]]; then
  exit 0
else
  exit 1
fi
