#!/usr/bin/env bash
# eval-doctor — read-only preflight for live MCP eval runs.
#
# Walks the dependency chain from tests/mcp_eval/README.md ("Live setup,
# step by step") and prints PASS/FAIL/WARN per check with the exact fix.
# Changes nothing. Exit 0 = ready for live runs (warnings allowed), 1 = not.
#
# Usage:
#   AFFINITY_API_KEY=<key> tests/mcp_eval/bin/eval-doctor [--fast]
#
# Env:
#   AFFINITY_API_KEY    dev API key for the eval org (some checks skip without it)
#   AFFINITY_MONOREPO   path to the affinity monorepo (default: ~/Documents/affinity/affinity)
#   EVAL_ORG_ID         eval org id (default: auto-detected via /v2/auth/whoami)
#   --fast              skip the slow feature-gate check (boots the Rails app)

set -u

MONOREPO="${AFFINITY_MONOREPO:-$HOME/Documents/affinity/affinity}"
API="https://api.dev.affinity.vc"
FAST=0
[[ "${1:-}" == "--fast" ]] && FAST=1
[[ "${1:-}" == "--help" || "${1:-}" == "-h" ]] && { sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }

PASS=0 FAIL=0 WARN=0

ok()   { printf '  \033[32mPASS\033[0m  %s\n' "$1"; PASS=$((PASS+1)); }
bad()  { printf '  \033[31mFAIL\033[0m  %s\n      fix: %s\n' "$1" "$2"; FAIL=$((FAIL+1)); }
warn() { printf '  \033[33mWARN\033[0m  %s\n      %s\n' "$1" "$2"; WARN=$((WARN+1)); }
section() { printf '\n\033[1m%s\033[0m\n' "$1"; }

http_code() { curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$1" 2>/dev/null || echo 000; }
container_up() { docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^$1$"; }
proc_up() { pgrep -f "$1" >/dev/null 2>&1; }

# ---------------------------------------------------------------- step 1
section "Step 1 — machine setup"

if container_up traefik; then ok "1.1 traefik container running"
else bad "1.1 traefik container not running" "start the local docker stack (monorepo)"; fi

if container_up opensearch-node1; then ok "1.2 opensearch-node1 container running"
else bad "1.2 opensearch-node1 not running" "run 'dev setup-global-search' in the monorepo (README 1.2)"; fi

# ---------------------------------------------------------------- step 2
section "Step 2 — services (mprocs panes)"

# Unauthenticated v1 probe against /organizations (the create_company /
# update_company endpoint, also what the 6.2 keyword-search check below
# hits directly): 401 means the service is up; 502 means the external-api
# pane is down, which every v1 tool (search_opportunities, get_reminders,
# create_company, etc.) depends on.
code=$(http_code "$API/organizations")
case "$code" in
  401|200) ok "2.0 external-api (v1) answering" ;;
  000)     bad "2.0 external-api (v1) unreachable (traefik down?)" "see step 1.1" ;;
  *)       bad "2.0 external-api (v1) down -> $code" "start the 'external-api' mprocs pane — every v1 tool (search_opportunities, get_reminders, create_company, etc.) needs it (README 2.0)" ;;
esac

# v2 is a separate pane; the v2 read tools (get_company_info, get_list_entries,
# get_person_info, search_companies_top_matches) all route through it.
code=$(http_code "$API/v2/companies")
case "$code" in
  401|200) ok "2.0 external-api-v2 answering" ;;
  000)     bad "2.0 external-api-v2 unreachable (traefik down?)" "see step 1.1" ;;
  *)       bad "2.0 external-api-v2 down -> $code" "start the 'external-api-v2' mprocs pane — v2 read tools and search_companies_top_matches need it (README 2.0)" ;;
esac

code=$(http_code "http://0.0.0.0:8098/healthz")
if [[ "$code" == 200 ]]; then ok "2.1 gs-search-client healthy (:8098/healthz)"
elif [[ "$code" == 000 ]]; then bad "2.1 gs-search-client down" "start the 'gs-search-client' mprocs pane (README 2.1)"
else bad "2.1 gs-search-client unhealthy (healthz -> $code)" "check the 'gs-search-client' mprocs pane logs (README 2.1)"; fi

for c in a b; do
  if proc_up "run-kafka-consumer-$c"; then ok "2.1 gs-kafka-consumer-$c running"
  else bad "2.1 gs-kafka-consumer-$c not running" "start the 'gs-kafka-consumer-$c' mprocs pane (README 2.1)"; fi
done

bifrost_up=0
code=$(http_code "https://bifrost-admin.dev.affinity.vc/")
case "$code" in
  000) bad "2.2 bifrost unreachable (traefik down?)" "see step 1.1" ;;
  502) bad "2.2 ai-gateway-bifrost down (traefik 502)" "start the 'ai-gateway-bifrost' mprocs pane after 'aws sso login' (README 2.2)" ;;
  *)   ok "2.2 ai-gateway-bifrost answering"; bifrost_up=1 ;;
esac

# The CLI session only matters when (re)starting bifrost — a running bifrost
# already holds its Bedrock credentials, so this is advisory.
if ! command -v aws >/dev/null 2>&1; then
  warn "1.3 aws CLI not found on PATH" "needed only to (re)start ai-gateway-bifrost"
elif aws sts get-caller-identity >/dev/null 2>&1; then
  ok "1.3 AWS CLI session valid"
elif [[ $bifrost_up -eq 1 ]]; then
  warn "1.3 AWS CLI session expired (bifrost is running on earlier creds)" "run 'aws sso login' before the next bifrost restart (README 1.3)"
else
  bad "1.3 AWS CLI session expired and bifrost is down" "run 'aws sso login' in a plain terminal, then start bifrost (README 1.3/2.2)"
fi

code=$(http_code "https://ai-search-client.dev.affinity.vc/healthz")
case "$code" in
  200) ok "2.2 ai-search-client healthy (/healthz)" ;;
  000) bad "2.2 ai-search-client unreachable (traefik down?)" "see step 1.1" ;;
  502) bad "2.2 ai-search-client down (traefik 502)" "start the 'ai-search-client' mprocs pane (README 2.2)" ;;
  *)   bad "2.2 ai-search-client unhealthy (healthz -> $code)" "check the 'ai-search-client' mprocs pane logs (README 2.2)" ;;
esac

if proc_up "ai_search.consumer.consumer"; then ok "2.2 ai-search-consumer running"
else bad "2.2 ai-search-consumer not running" "start the 'ai-search-consumer' mprocs pane; if it dies with 'address already in use', kill the orphan holding the port (README troubleshooting)"; fi

if proc_up "run_outbox_processor"; then ok "2.3 kafka-outbox-primary running"
else bad "2.3 kafka-outbox-primary not running" "start the 'kafka-outbox-primary' mprocs pane — without it company events sit in the outbox forever (README 2.3)"; fi

# bin/run_eventing_consumers.rb execs into `karafka server`, so the script
# name never appears in the process table; karafka is unique to this pane.
if proc_up "karafka server"; then ok "2.3 eventing-consumers running (karafka server)"
else bad "2.3 eventing-consumers not running" "start the 'eventing-consumers' mprocs pane (README 2.3)"; fi

# ---------------------------------------------------------------- org id
ORG_ID="${EVAL_ORG_ID:-}"
ORG_SUBDOMAIN=""
if [[ -n "${AFFINITY_API_KEY:-}" ]]; then
  whoami_json=$(curl -s --max-time 5 -H "Authorization: Bearer $AFFINITY_API_KEY" "$API/v2/auth/whoami" 2>/dev/null)
  [[ -z "$ORG_ID" ]] && ORG_ID=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["tenant"]["id"])' <<<"$whoami_json" 2>/dev/null || true)
  ORG_SUBDOMAIN=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["tenant"]["subdomain"])' <<<"$whoami_json" 2>/dev/null || true)
fi

# ---------------------------------------------------------------- step 3
section "Step 3 — feature gates"

if [[ $FAST -eq 1 ]]; then
  warn "3.x gate check skipped (--fast)" "rerun without --fast to verify the domain-event gates"
elif [[ ! -d "$MONOREPO" ]]; then
  bad "3.x monorepo not found at $MONOREPO" "set AFFINITY_MONOREPO to your checkout"
elif [[ -z "$ORG_ID" ]]; then
  warn "3.x gate check skipped" "set AFFINITY_API_KEY (or EVAL_ORG_ID) so the org gate can be checked"
else
  gates_out=$(cd "$MONOREPO" && echo "
    puts \"GATE_EVERYONE=#{Affinity::Gating.enabled_for_everyone?(Affinity::Gating::COMPANY_INTERNAL_LOW_PRIORITY_DOMAIN_EVENT_CONSUMER)}\"
    puts \"GATE_FANOUT=#{Affinity::Gating.enabled_for_everyone?(Affinity::Gating::COMPANY_GLOBAL_DOMAIN_EVENT_PROCESSING_AND_FANOUT)}\"
    puts \"GATE_ORG=#{Affinity::Gating.enabled_for_org_id?($ORG_ID, Affinity::Gating::COMPANY_DOMAIN_EVENT_ENABLED_ORGS)}\"
    exit
  " | mise exec -- bin/repl 2>/dev/null)
  for g in GATE_EVERYONE:company-internal-low-priority-domain-event-consumer \
           GATE_FANOUT:company-global-domain-event-processing-and-fanout \
           GATE_ORG:company-domain-event-enabled-orgs; do
    key="${g%%:*}"; name="${g#*:}"
    if grep -q "${key}=true" <<<"$gates_out"; then ok "3.x gate '$name' enabled"
    else bad "3.x gate '$name' NOT enabled" "enable it in bin/repl, then restart eventing-consumers (README step 3)"; fi
  done
fi

# ---------------------------------------------------------------- steps 4+6
section "Steps 4 & 6 — org, seed, and search paths"

if [[ -z "${AFFINITY_API_KEY:-}" ]]; then
  warn "4.2 AFFINITY_API_KEY not set" "export it to check the org, seed data, and search paths (README 4.2)"
else
  body=$(curl -s --max-time 10 -H "Authorization: Bearer $AFFINITY_API_KEY" "$API/v2/companies?limit=100" 2>/dev/null)
  if grep -q "Meridian Biotech" <<<"$body"; then ok "4.3 seed data visible via API (org $ORG_ID)"
  elif grep -q '"data"' <<<"$body"; then bad "4.3 API key works but seed data missing" "seed the org (README 4.3)"
  else bad "4.2/6.1 API not answering with this key" "check the key, and that the stack is up (README 4.2 / 6.1)"; fi

  if [[ -d "$MONOREPO" && -n "$ORG_SUBDOMAIN" && ! -f "$MONOREPO/tmp/manifest-$ORG_SUBDOMAIN.json" ]]; then
    warn "4.3 seed manifest not found at tmp/manifest-$ORG_SUBDOMAIN.json" "teardown will not work without it; reseed with --manifest-file tmp/manifest-$ORG_SUBDOMAIN.json if you ever need to reset"
  fi

  kw=$(curl -s --max-time 10 -u ":$AFFINITY_API_KEY" "$API/organizations?term=Meridian+Biotech" 2>/dev/null)
  if grep -q "Meridian Biotech" <<<"$kw"; then ok "6.2 keyword search returns seeded org data"
  elif grep -qi "bad gateway" <<<"$kw"; then bad "6.2 keyword search: Bad Gateway (note: the live smoke test can still pass — the agent routes around this via search_companies_top_matches's semantic path)" "if check 2.0 failed, start the 'external-api' pane first; otherwise OpenSearch or gs-search-client is unhealthy (README 1.2 / 2.1) — and if the OpenSearch container was recreated its indices are empty (re-run 'dev setup-global-search', then teardown+reseed)"
  else bad "6.2 keyword search returns no seed hit" "gs consumers may not have indexed the seed; check the gs-kafka-consumer panes (README 2.1)"; fi

  sem=$(curl -s --max-time 30 -X POST -H "Authorization: Bearer $AFFINITY_API_KEY" -H "Content-Type: application/json" \
    -d '{"prompt":"biotech companies","limit":5,"entityType":"companies"}' "$API/v2/semantic-search" 2>/dev/null)
  if grep -q "Meridian Biotech" <<<"$sem"; then ok "6.3 semantic search returns seeded org data"
  elif grep -q '"data":\s*\[\]' <<<"$sem"; then bad "6.3 semantic search succeeds but is EMPTY" "embeddings missing: check gates (step 3), outbox/eventing panes (2.3), then run the backfill (README step 5)"
  elif grep -q '"data"' <<<"$sem"; then warn "6.3 semantic search answers but without the seeded biotech company" "embeddings may still be indexing; re-check in a minute"
  else bad "6.3 semantic search erroring" "ai-search-client or bifrost unhealthy (README 2.2)"; fi
fi

# ---------------------------------------------------------------- tooling
section "Tooling (this repo)"

command -v claude >/dev/null 2>&1 && ok "claude CLI on PATH" || bad "claude CLI not found" "install Claude Code (needed by the provider)"
command -v uv >/dev/null 2>&1 && ok "uv on PATH" || bad "uv not found" "install uv"

# ---------------------------------------------------------------- summary
printf '\n\033[1mSummary:\033[0m %d passed, %d failed, %d warnings\n' "$PASS" "$FAIL" "$WARN"
if [[ $FAIL -eq 0 ]]; then
  printf 'Ready for live runs: MCP_EVAL_LIVE=1 AFFINITY_API_KEY=<key> uv run pytest tests/mcp_eval/ -m live -s\n'
  exit 0
else
  printf 'Not ready — fix the FAILs above (top to bottom: each step depends on the previous).\n'
  exit 1
fi
