#!/usr/bin/env bash
# gstack-gbrain-mcp-verify — probe a remote gbrain MCP endpoint.
#
# Usage:
#   GBRAIN_MCP_TOKEN=<bearer> gstack-gbrain-mcp-verify <url>
#
# Output (always valid JSON):
#   {
#     "status": "success" | "network" | "auth" | "malformed",
#     "server_name": "gbrain" | null,
#     "server_version": "0.26.8" | null,
#     "error_class": "NETWORK" | "AUTH" | "MALFORMED" | null,
#     "error_text": "<remediation hint + raw>" | null,
#     "sources_add_url_supported": true | false,
#     "raw_initialize_body": "<full body for debugging>" | null
#   }
#
# Token is consumed from the GBRAIN_MCP_TOKEN env var, never argv. Prevents
# shell-history / `ps` exposure of the bearer.
#
# Three error classes:
#   NETWORK   — DNS / TCP / no HTTP response
#   AUTH      — 401, 403, or 500 with stale-token-shaped body
#   MALFORMED — 2xx but missing serverInfo, OR `Not Acceptable` (the dual
#               Accept-header gotcha)
#
# `sources_add_url_supported` probes capability via tools/list — true iff the
# remote exposes `mcp__gbrain__sources_add` (gbrain hasn't shipped this as
# of v0.26.x; field is forward-compatible).
#
# Exit codes: 0 on success, 1 on classified failure, 2 on usage error.
set -euo pipefail

die_usage() {
  echo "Usage: GBRAIN_MCP_TOKEN=<bearer> gstack-gbrain-mcp-verify <url>" >&2
  exit 2
}

[ $# -eq 1 ] || die_usage
URL="$1"
[ -n "${GBRAIN_MCP_TOKEN:-}" ] || { echo "gstack-gbrain-mcp-verify: GBRAIN_MCP_TOKEN env var required" >&2; exit 2; }

command -v curl >/dev/null 2>&1 || { echo "gstack-gbrain-mcp-verify: curl is required" >&2; exit 2; }
command -v jq >/dev/null 2>&1 || { echo "gstack-gbrain-mcp-verify: jq is required (brew install jq)" >&2; exit 2; }

emit() {
  # emit <status> <server_name> <server_version> <error_class> <error_text> <url_supported> <raw_body>
  jq -n \
    --arg status "$1" \
    --arg server_name "${2:-}" \
    --arg server_version "${3:-}" \
    --arg error_class "${4:-}" \
    --arg error_text "${5:-}" \
    --argjson url_supported "${6:-false}" \
    --arg raw "${7:-}" \
    '{
      status: $status,
      server_name: (if $server_name == "" then null else $server_name end),
      server_version: (if $server_version == "" then null else $server_version end),
      error_class: (if $error_class == "" then null else $error_class end),
      error_text: (if $error_text == "" then null else $error_text end),
      sources_add_url_supported: $url_supported,
      raw_initialize_body: (if $raw == "" then null else $raw end)
    }'
}

# JSON-RPC initialize body. Both `application/json` AND `text/event-stream`
# in Accept — the MCP server returns 406 Not Acceptable without both. The
# transcript that motivated this script hit that exact failure.
INIT_BODY='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"gstack-mcp-verify","version":"1"}}}'

# Capture HTTP code + body in one pass; --max-time 10 caps total wall time.
TMPBODY=$(mktemp -t gstack-mcp-verify.XXXXXX)
trap 'rm -f "$TMPBODY"' EXIT

set +e
HTTP_CODE=$(curl -s -o "$TMPBODY" -w '%{http_code}' \
  --max-time 10 \
  -X POST \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H "Authorization: Bearer $GBRAIN_MCP_TOKEN" \
  -d "$INIT_BODY" \
  "$URL" 2>/dev/null)
CURL_EXIT=$?
set -e

BODY=$(cat "$TMPBODY" 2>/dev/null || echo "")

# --- NETWORK class: curl exited nonzero, no HTTP response ---
if [ "$CURL_EXIT" -ne 0 ] || [ -z "$HTTP_CODE" ] || [ "$HTTP_CODE" = "000" ]; then
  HOST=$(echo "$URL" | sed -E 's|^https?://([^/:]+).*|\1|')
  emit "network" "" "" "NETWORK" "check Tailscale/DNS to ${HOST} (curl exit=${CURL_EXIT})" false "$BODY"
  exit 1
fi

# --- AUTH class: 401, 403, or 500 with stale-token-shaped body ---
case "$HTTP_CODE" in
  401|403)
    emit "auth" "" "" "AUTH" "rotate token on the brain host, re-run /setup-gbrain (HTTP $HTTP_CODE)" false "$BODY"
    exit 1
    ;;
  500)
    if echo "$BODY" | grep -qiE '"(error_description|message)":[[:space:]]*"[^"]*(auth|token|unauthorized)' 2>/dev/null; then
      emit "auth" "" "" "AUTH" "rotate token on the brain host, re-run /setup-gbrain (HTTP 500 stale-token shape)" false "$BODY"
      exit 1
    fi
    ;;
esac

# Anything not 2xx that isn't auth-shaped → MALFORMED with raw HTTP code.
case "$HTTP_CODE" in
  2*) ;;
  *)
    emit "malformed" "" "" "MALFORMED" "server returned HTTP $HTTP_CODE; verify URL + version compatibility" false "$BODY"
    exit 1
    ;;
esac

# --- 2xx path: body may be JSON or SSE-wrapped JSON. Strip SSE if present. ---
# MCP servers return SSE format: `event: message\ndata: {...}\n\n`. Extract
# just the JSON payload from the data: line, falling back to the body as-is.
if echo "$BODY" | head -1 | grep -q '^event:'; then
  JSON_BODY=$(echo "$BODY" | sed -n 's/^data: //p' | head -1)
else
  JSON_BODY="$BODY"
fi

# `Not Acceptable` is a JSON-RPC error from the MCP server itself, returned
# with HTTP 200 if the SSE Accept header was missing. Detect it explicitly.
if echo "$JSON_BODY" | jq -e '.error.message | test("[Nn]ot [Aa]cceptable")' >/dev/null 2>&1; then
  emit "malformed" "" "" "MALFORMED" "Accept-header gotcha: pass both 'application/json' AND 'text/event-stream'" false "$BODY"
  exit 1
fi

SERVER_NAME=$(echo "$JSON_BODY" | jq -r '.result.serverInfo.name // empty' 2>/dev/null)
SERVER_VERSION=$(echo "$JSON_BODY" | jq -r '.result.serverInfo.version // empty' 2>/dev/null)

if [ -z "$SERVER_NAME" ] || [ -z "$SERVER_VERSION" ]; then
  emit "malformed" "" "" "MALFORMED" "server may be on a newer gbrain version; missing result.serverInfo. Verify with: curl -H 'Accept: application/json, text/event-stream'" false "$BODY"
  exit 1
fi

# --- Capability probe: tools/list to detect sources_add ---
# Best-effort. A failure here doesn't fail the verify; we just default
# sources_add_url_supported=false. Future gbrain versions that ship
# mcp__gbrain__sources_add will flip this true and gstack-artifacts-init
# will print the one-liner form instead of the clone-then-path form.
URL_SUPPORTED=false
TOOLS_BODY_FILE=$(mktemp -t gstack-mcp-tools.XXXXXX)
TOOLS_REQ='{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

set +e
curl -s -o "$TOOLS_BODY_FILE" \
  --max-time 10 \
  -X POST \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H "Authorization: Bearer $GBRAIN_MCP_TOKEN" \
  -d "$TOOLS_REQ" \
  "$URL" >/dev/null 2>&1
TOOLS_EXIT=$?
set -e

if [ "$TOOLS_EXIT" -eq 0 ]; then
  TOOLS_BODY=$(cat "$TOOLS_BODY_FILE" 2>/dev/null || echo "")
  if echo "$TOOLS_BODY" | head -1 | grep -q '^event:'; then
    TOOLS_JSON=$(echo "$TOOLS_BODY" | sed -n 's/^data: //p' | head -1)
  else
    TOOLS_JSON="$TOOLS_BODY"
  fi
  if echo "$TOOLS_JSON" | jq -e '.result.tools[] | select(.name | test("sources_add"))' >/dev/null 2>&1; then
    URL_SUPPORTED=true
  fi
fi
rm -f "$TOOLS_BODY_FILE"

emit "success" "$SERVER_NAME" "$SERVER_VERSION" "" "" "$URL_SUPPORTED" "$BODY"
exit 0
