#!/usr/bin/env bash
# Smoke-test the editor_reload_plugin tool in CI.
# 1. Creates a node (cube) via the OLD plugin
# 2. Reloads the plugin, verifies new session ID
# 3. Checks the log buffer is fresh (proves handlers were rebuilt)
# 4. Finds the cube via the NEW plugin (proves scene tree continuity)
#
# Expects: Python server running on port 8000, Godot editor with plugin connected.
set -euo pipefail
source "$(dirname "$0")/_ci_env.sh"

SERVER_URL="http://127.0.0.1:8000/mcp"
HEADERS=(-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream")
REQ_ID=0

# Helper: call an MCP tool and extract the content JSON from the SSE response.
# Prints parsed content to stdout; diagnostics to stderr. Returns 1 on failure.
mcp_call() {
  local tool="$1"
  local args="$2"
  REQ_ID=$((REQ_ID + 1))
  local raw
  raw=$(curl -s --max-time 30 "$SERVER_URL" -X POST "${HEADERS[@]}" \
    -H "Mcp-Session-Id: $SESSION_ID" \
    -d "{\"jsonrpc\":\"2.0\",\"id\":$REQ_ID,\"method\":\"tools/call\",\"params\":{\"name\":\"$tool\",\"arguments\":$args}}")
  echo "$raw" | python3 -c "
import json, sys
raw = sys.stdin.read()
for line in raw.split('\n'):
    if line.startswith('data: '):
        data = json.loads(line[6:])
        result = data.get('result', {})
        if result.get('isError'):
            msg = result.get('content', [{}])[0].get('text', 'unknown error')
            print(f'Tool error: {msg}', file=sys.stderr)
            print('{}')
            sys.exit(1)
        content = result.get('structuredContent', {})
        if not content:
            text = result.get('content', [{}])[0].get('text', '{}')
            content = json.loads(text)
        print(json.dumps(content))
        sys.exit(0)
print(f'No SSE data in response (first 500 chars): {raw[:500]}', file=sys.stderr)
print('{}')
sys.exit(1)
"
}

# Helper: call mcp_call and capture output. On failure, print the captured output and exit.
mcp_call_or_die() {
  local label="$1"
  shift
  local result
  if ! result=$(mcp_call "$@"); then
    echo "FAIL at $label (tool: $1)"
    echo "Captured output: $result"
    exit 1
  fi
  echo "$result"
}

# Initialize MCP session
echo "Initializing MCP session..."
SESSION_ID=$(curl -si "$SERVER_URL" -X POST "${HEADERS[@]}" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"ci-reload","version":"1.0"}}}' \
  2>&1 | grep -i 'mcp-session-id' | tr -d '\r' | awk '{print $2}')

if [ -z "$SESSION_ID" ]; then
  echo "ERROR: Could not initialize MCP session"
  exit 1
fi

curl -s "$SERVER_URL" -X POST "${HEADERS[@]}" \
  -H "Mcp-Session-Id: $SESSION_ID" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' > /dev/null

# --- Step 1: Create a cube with the OLD plugin ---
echo "Creating test node with old plugin..."
CREATE_RESULT=$(mcp_call_or_die "node_create" node_create '{"type":"MeshInstance3D","name":"ReloadTestCube","parent_path":""}')
echo "$CREATE_RESULT" | python3 -c "
import json, sys
content = json.loads(sys.stdin.read())
path = content.get('path', '')
if not path:
    print(f'FAIL: node_create did not return a path: {content}')
    sys.exit(1)
print(f'Created node: {path}')
"

# --- Step 2: Reload the plugin ---
echo "Calling editor_reload_plugin..."
RELOAD_RESULT=$(mcp_call_or_die "editor_reload_plugin" editor_reload_plugin '{}')
echo "$RELOAD_RESULT" | python3 -c "
import json, sys
content = json.loads(sys.stdin.read())
status = content.get('status', '')
old_id = content.get('old_session_id', '')
new_id = content.get('new_session_id', '')
if status != 'reloaded':
    print(f'FAIL: editor_reload_plugin returned status={status!r}, expected \"reloaded\"')
    print(f'Full response: {content}')
    sys.exit(1)
if old_id == new_id:
    print(f'FAIL: old and new session IDs are the same: {old_id}')
    sys.exit(1)
print(f'editor_reload_plugin OK: {old_id[:12]}... -> {new_id[:12]}...')
"

# --- Step 3: Verify log buffer is fresh (proves plugin fully rebuilt) ---
echo "Checking log buffer..."
LOGS_RESULT=$(mcp_call_or_die "logs_read" logs_read '{"count":20}')
echo "$LOGS_RESULT" | python3 -c "
import json, sys
content = json.loads(sys.stdin.read())
lines = content.get('lines', [])
total = content.get('total_count', -1)
if not lines:
    print('FAIL: logs_read returned no lines after reload')
    sys.exit(1)
if 'plugin loaded' not in lines[0]:
    print(f'FAIL: first log line should be \"plugin loaded\", got: {lines[0]!r}')
    sys.exit(1)
if total > 20:
    print(f'WARN: log buffer has {total} lines — expected a small fresh buffer')
print(f'Log buffer OK: {total} lines, starts with \"{lines[0]}\"')
"

# --- Step 4: Find the cube with the NEW plugin (scene tree survived) ---
echo "Finding test node with new plugin..."
FIND_RESULT=$(mcp_call_or_die "node_find" node_find '{"name":"ReloadTestCube"}')
echo "$FIND_RESULT" | python3 -c "
import json, sys
content = json.loads(sys.stdin.read())
nodes = content.get('nodes', [])
if not nodes:
    print('FAIL: ReloadTestCube not found after reload — scene tree did not survive')
    sys.exit(1)
node = nodes[0]
print(f'Found node: {node.get(\"name\", \"?\")} ({node.get(\"type\", \"?\")})')
"

# --- Step 5: Verify editor_state works ---
echo "Verifying editor_state..."
HEALTH_RESULT=$(mcp_call_or_die "editor_state" editor_state '{}')
echo "$HEALTH_RESULT" | python3 -c "
import json, sys
content = json.loads(sys.stdin.read())
version = content.get('godot_version', '')
project = content.get('project_name', '')
if not version:
    print(f'FAIL: editor_state missing godot_version after reload')
    print(f'Full response: {content}')
    sys.exit(1)
print(f'Post-reload health OK: {project} on Godot {version}')
"

# --- Step 6: Exercise the game-logs path (issue #46 crash site) ---
# Before the cleanup fix in _exit_tree, logs_read(source=game) on the
# new plugin would segfault at McpGameLogBuffer.get_range's first _storage
# access. This asserts the handler's typed array stays valid across a reload.
echo "Verifying logs_read source=game..."
GAMELOGS_RESULT=$(mcp_call_or_die "logs_read source=game" logs_read '{"count":10,"source":"game"}')
echo "$GAMELOGS_RESULT" | python3 -c "
import json, sys
content = json.loads(sys.stdin.read())
# A fresh editor has no game logs yet — we only care the call returned
# without SIGSEGV and reports a sane shape. 'source' should echo 'game'.
if content.get('source') != 'game':
    print(f'FAIL: logs_read source=game returned wrong source: {content}')
    sys.exit(1)
total = content.get('total_count', -1)
if total < 0:
    print(f'FAIL: logs_read source=game missing total_count: {content}')
    sys.exit(1)
print(f'Game-logs path OK: total={total}, returned={content.get(\"returned_count\", 0)}')
"

# --- Step 7: Multi-reload churn (cumulative corruption regression) ---
# The original issue report saw crashes after ~10 reloads. Do 9 more
# reloads (10 total with step 2 above) interleaved with cheap tool
# calls — catches both single-reload and cumulative-state regressions.
echo "Running 9 more reload iterations..."
for i in $(seq 2 10); do
  mcp_call_or_die "editor_reload_plugin #$i" editor_reload_plugin '{}' > /dev/null
  # Rotate the post-reload probe across the three reported-stable tools.
  case $(( i % 3 )) in
    0)
      mcp_call_or_die "editor_state after reload #$i" editor_state '{}' > /dev/null
      ;;
    1)
      mcp_call_or_die "node_find after reload #$i" node_find '{"name":"ReloadTestCube"}' > /dev/null
      ;;
    2)
      mcp_call_or_die "logs_read source=game after reload #$i" logs_read '{"count":5,"source":"game"}' > /dev/null
      ;;
  esac
  echo "  reload #$i OK"
done

# --- Step 8: Post-churn test_run (ResourceSaver.save path from issue #46) ---
# The original crash report was "~10 reloads then run the test suite" —
# the failing backtrace went through ResourceSaver::save during test
# cleanup. Running the suite here exercises that exact path on top of
# all the accumulated reload state.
#
# Exclude Camera2D current-state timing assertions here only. They remain in
# the normal handler test pass above; reload smoke is guarding reload churn and
# cleanup survivability, not synchronous viewport-current timing after undo.
# Both excluded tests pass green in `Run handler tests` but flake on Windows
# in the post-reload-churn re-run (refs #278 / PR #296). The comma-separated
# form is parsed by `McpTestRunner._parse_exclusions` — see
# `plugin/addons/godot_ai/testing/test_runner.gd`.
echo "Running GDScript test suite on reloaded plugin..."
TESTS_RESULT=$(mcp_call_or_die "test_run post-churn" test_run '{"verbose":false,"exclude_test_name":"test_configure_current_sibling_unmark_single_undo,test_get_returns_current_when_path_empty"}')
echo "$TESTS_RESULT" | python3 -c "
import json, sys
content = json.loads(sys.stdin.read())
failed = content.get('failed', -1)
passed = content.get('passed', 0)
skipped = content.get('skipped', 0)
if failed != 0:
    print(f'FAIL: {failed} test(s) failed after reload churn (passed={passed})')
    for f in content.get('failures', []):
        print(f'  FAIL: {f[\"suite\"]}.{f[\"test\"]}: {f[\"message\"]}')
    sys.exit(1)
print(f'Post-churn test suite OK: {passed} passed, {failed} failed, {skipped} skipped')
"

echo "Reload smoke test passed"
