#!/usr/bin/env bash
# E2E Integration Test Script for knot
# 
# This script tests the complete indexing and MCP query pipeline:
# 1. Spins up isolated Neo4j and Qdrant instances on high ports (17xxx/16xxx)
# 2. Indexes test files from tests/testing_files/
# 3. Queries the MCP server to validate decorator and type reference extraction
# 4. Cleans up containers and data
#
# Usage: ./tests/run_e2e.sh
# Requirements: docker, docker-compose, jq (optional for JSON parsing)

set -e  # Exit on error
set -u  # Exit on undefined variable

# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
COMPOSE_FILE="$SCRIPT_DIR/docker-compose.e2e.yml"
TEST_FILES_DIR="$SCRIPT_DIR/testing_files"
E2E_DATA_DIR="$SCRIPT_DIR/.e2e_data"

# Database configuration (high ports to avoid conflicts)
NEO4J_URI="bolt://localhost:17687"
NEO4J_USER="neo4j"
NEO4J_PASSWORD="e2e_test_password"
QDRANT_URL="http://localhost:16334"
QDRANT_COLLECTION="knot_e2e_test"
REPO_NAME="e2e_test_repo"

# Timeout settings
TIMEOUT_SECONDS=60
HEALTH_CHECK_INTERVAL=2

echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}knot E2E Integration Test${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""

# Cleanup function (runs on exit)
cleanup() {
    local exit_code=$?
    
    if [ $exit_code -ne 0 ]; then
        echo -e "\n${RED}Tests failed! Leaving containers running for inspection.${NC}"
        echo -e "${YELLOW}To inspect manually:${NC}"
        echo "  Neo4j:  docker exec -it knot_neo4j_e2e cypher-shell -u neo4j -p e2e_test_password"
        echo "  Qdrant: curl http://localhost:16334/collections/knot_e2e_test"
        echo ""
        echo -e "${YELLOW}To clean up manually when done:${NC}"
        echo "  cd $SCRIPT_DIR && docker compose -f docker-compose.e2e.yml down -v"
        echo "  sudo rm -rf $E2E_DATA_DIR"
        return 0
    fi
    
    echo -e "\n${YELLOW}Cleaning up...${NC}"
    cd "$SCRIPT_DIR"
    docker compose -f "$COMPOSE_FILE" down -v 2>/dev/null || true
    # Use sudo to remove files created by Docker containers (owned by root)
    if [ -d "$E2E_DATA_DIR" ]; then
        sudo rm -rf "$E2E_DATA_DIR" 2>/dev/null || rm -rf "$E2E_DATA_DIR" 2>/dev/null || true
    fi
    echo -e "${GREEN}Cleanup complete${NC}"
}

# Register cleanup on script exit
trap cleanup EXIT INT TERM

# Step 1: Start Docker containers
echo -e "${YELLOW}[1/5] Starting Docker containers...${NC}"
cd "$SCRIPT_DIR"
docker compose -f "$COMPOSE_FILE" down -v 2>/dev/null || true
# Clean up data directory (use sudo if regular rm fails due to Docker file ownership)
if [ -d "$E2E_DATA_DIR" ]; then
    sudo rm -rf "$E2E_DATA_DIR" 2>/dev/null || rm -rf "$E2E_DATA_DIR" 2>/dev/null || true
fi
docker compose -f "$COMPOSE_FILE" up -d

# Step 2: Wait for services to be ready
echo -e "${YELLOW}[2/5] Waiting for services to be ready...${NC}"

wait_for_port() {
    local port=$1
    local service=$2
    local container=$3
    local elapsed=0
    
    echo -n "Waiting for $service"
    while true; do
        # Check if Docker container is healthy (Neo4j) or port is open (Qdrant)
        if [ "$service" = "Neo4j" ]; then
            local status
            status=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "starting")
            if [ "$status" = "healthy" ]; then
                echo ""
                echo -e "${GREEN}✓ $service is ready (healthy)${NC}"
                return 0
            fi
        else
            if nc -z localhost "$port" 2>/dev/null; then
                echo ""
                echo -e "${GREEN}✓ $service is ready on port $port${NC}"
                return 0
            fi
        fi
        
        if [ $elapsed -ge $TIMEOUT_SECONDS ]; then
            echo ""
            echo -e "${RED}ERROR: $service did not start within ${TIMEOUT_SECONDS}s${NC}"
            return 1
        fi
        sleep $HEALTH_CHECK_INTERVAL
        elapsed=$((elapsed + HEALTH_CHECK_INTERVAL))
        echo -n "."
    done
}

wait_for_port 17687 "Neo4j" "knot_neo4j_e2e"
wait_for_port 16334 "Qdrant" "knot_qdrant_e2e"

# Give databases extra time to fully initialize
echo "Waiting 5 seconds for databases to fully initialize..."
sleep 5

# Step 3: Build and run indexer
echo -e "${YELLOW}[3/5] Indexing test files...${NC}"
cd "$PROJECT_ROOT"

export KNOT_REPO_PATH="$TEST_FILES_DIR"
export KNOT_REPO_NAME="$REPO_NAME"
export KNOT_NEO4J_URI="$NEO4J_URI"
export KNOT_NEO4J_USER="$NEO4J_USER"
export KNOT_NEO4J_PASSWORD="$NEO4J_PASSWORD"
export KNOT_QDRANT_URL="$QDRANT_URL"
export KNOT_QDRANT_COLLECTION="$QDRANT_COLLECTION"

# Build in release mode for faster execution
echo "Building knot-indexer..."
cargo build --release --bin knot-indexer 2>&1 | grep -E "(Compiling|Finished|error)" || true

echo "Running indexer..."
cargo run --release --bin knot-indexer -- --clean

echo -e "${GREEN}✓ Indexing complete${NC}"

# Step 4: Query MCP server and CLI to validate results
echo -e "${YELLOW}[4/6] Building query tools...${NC}"

# Build both knot-mcp and knot CLI
echo "Building knot-mcp..."
cargo build --release --bin knot-mcp 2>&1 | grep -E "(Compiling|Finished|error)" || true

echo "Building knot CLI..."
cargo build --release --bin knot 2>&1 | grep -E "(Compiling|Finished|error)" || true

echo -e "${GREEN}✓ All tools built successfully${NC}"

# Test 1: Explore TypeScript file - should find decorators and type references
echo ""
echo "Test 1: Exploring test_typescript.ts (via MCP and CLI)..."
# Note: explore_file expects an absolute path
TS_FILE="$TEST_FILES_DIR/test_typescript.ts"
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"explore_file\",\"arguments\":{\"file_path\":\"$TS_FILE\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- explore "$TS_FILE" 2>/dev/null)

# Check if MCP response contains expected entities
if echo "$MCP_RESPONSE" | grep -q "AppComponent"; then
    echo -e "${GREEN}✓ MCP: Found AppComponent${NC}"
else
    echo -e "${RED}✗ MCP: AppComponent not found in response${NC}"
    echo "Response: $MCP_RESPONSE"
    exit 1
fi

if echo "$MCP_RESPONSE" | grep -q "AnalyticsService"; then
    echo -e "${GREEN}✓ MCP: Found AnalyticsService${NC}"
else
    echo -e "${RED}✗ MCP: AnalyticsService not found in response${NC}"
    exit 1
fi

# Check if CLI response contains expected entities
if echo "$CLI_RESPONSE" | grep -q "AppComponent"; then
    echo -e "${GREEN}✓ CLI: Found AppComponent${NC}"
else
    echo -e "${RED}✗ CLI: AppComponent not found in response${NC}"
    echo "Response: $CLI_RESPONSE"
    exit 1
fi

if echo "$CLI_RESPONSE" | grep -q "AnalyticsService"; then
    echo -e "${GREEN}✓ CLI: Found AnalyticsService${NC}"
else
    echo -e "${RED}✗ CLI: AnalyticsService not found in response${NC}"
    exit 1
fi

# Test 2: Find callers of AppComponent (should be referenced by AppModule decorator)
echo ""
echo "Test 2: Finding callers of AppComponent (via MCP and CLI)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"AppComponent\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "AppComponent" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "AppModule"; then
    echo -e "${GREEN}✓ MCP: AppModule references AppComponent (decorator extraction works!)${NC}"
else
    echo -e "${RED}✗ MCP: AppModule reference not found (decorator extraction failed)${NC}"
    echo "Response: $MCP_RESPONSE"
    exit 1
fi

if echo "$CLI_RESPONSE" | grep -q "AppModule"; then
    echo -e "${GREEN}✓ CLI: AppModule references AppComponent${NC}"
else
    echo -e "${RED}✗ CLI: AppModule reference not found${NC}"
    echo "Response: $CLI_RESPONSE"
    exit 1
fi

# Test 3: Search for UserService in Java
echo ""
echo "Test 3: Searching for UserService in Java files (via MCP and CLI)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"UserService\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "UserService" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "UserService"; then
    echo -e "${GREEN}✓ MCP: Found UserService in search results${NC}"
else
    echo -e "${RED}✗ MCP: UserService not found${NC}"
    exit 1
fi

if echo "$CLI_RESPONSE" | grep -q "UserService"; then
    echo -e "${GREEN}✓ CLI: Found UserService in search results${NC}"
else
    echo -e "${RED}✗ CLI: UserService not found${NC}"
    exit 1
fi

# Test 4: Explore JavaScript file
echo ""
echo "Test 4: Exploring test_javascript.jsx (via MCP and CLI)..."
JSX_FILE="$TEST_FILES_DIR/test_javascript.jsx"
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"tools/call\",\"params\":{\"name\":\"explore_file\",\"arguments\":{\"file_path\":\"$JSX_FILE\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- explore "$JSX_FILE" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "DataService"; then
    echo -e "${GREEN}✓ MCP: Found DataService in JavaScript file${NC}"
else
    echo -e "${RED}✗ MCP: DataService not found${NC}"
    exit 1
fi

if echo "$CLI_RESPONSE" | grep -q "DataService"; then
    echo -e "${GREEN}✓ CLI: Found DataService in JavaScript file${NC}"
else
    echo -e "${RED}✗ CLI: DataService not found${NC}"
    exit 1
fi

# Test 5: Search for HTML elements and attributes from Angular file
echo ""
echo "Test 5: Searching for HTML elements and attributes in test_angular.html (via MCP and CLI)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"app-header\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "app-header" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "app-header"; then
    echo -e "${GREEN}✓ MCP: Found app-header custom element${NC}"
else
    echo -e "${RED}✗ MCP: app-header custom element not found${NC}"
    exit 1
fi

if echo "$CLI_RESPONSE" | grep -q "app-header"; then
    echo -e "${GREEN}✓ CLI: Found app-header custom element${NC}"
else
    echo -e "${RED}✗ CLI: app-header custom element not found${NC}"
    exit 1
fi

# Test for HTML id attribute
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":6,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"dashboard\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "dashboard" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "dashboard" && echo "$CLI_RESPONSE" | grep -q "dashboard"; then
    echo -e "${GREEN}✓ Found HTML id 'dashboard' (both MCP and CLI)${NC}"
else
    echo -e "${RED}✗ HTML id 'dashboard' not found${NC}"
    exit 1
fi

# Test for HTML class attribute
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":7,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"navbar\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "navbar" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "navbar" && echo "$CLI_RESPONSE" | grep -q "navbar"; then
    echo -e "${GREEN}✓ Found HTML class 'navbar' (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ HTML class 'navbar' not found${NC}"
    exit 1
fi

# Test 6: Search for JSX attributes from JavaScript file
echo ""
echo "Test 6: Searching for JSX attributes in test_javascript.jsx..."
# Search for id attribute
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":8,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"chart-toolbar\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "chart-toolbar" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "chart-toolbar" && echo "$CLI_RESPONSE" | grep -q "chart-toolbar"; then
    echo -e "${GREEN}✓ Found JSX id 'chart-toolbar' (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ JSX id 'chart-toolbar' not found${NC}"
    exit 1
fi

# Search for className attribute
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":9,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"btn-primary\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "btn-primary" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "btn-primary" && echo "$CLI_RESPONSE" | grep -q "btn-primary"; then
    echo -e "${GREEN}✓ Found JSX className 'btn-primary' (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ JSX className 'btn-primary' not found${NC}"
    exit 1
fi

# Test for multiple classes in JSX
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":10,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"profile-card\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "profile-card" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "profile-card" && echo "$CLI_RESPONSE" | grep -q "profile-card"; then
    echo -e "${GREEN}✓ Found JSX className 'profile-card' (multiple classes, MCP & CLI)${NC}"
else
    echo -e "${RED}✗ JSX className 'profile-card' not found${NC}"
    exit 1
fi

# Test 7: Search for CSS classes
echo ""
echo "Test 7: Searching for CSS classes in test_styles.css..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":11,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"btn-primary\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "btn-primary" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "btn-primary" && echo "$CLI_RESPONSE" | grep -q "btn-primary"; then
    echo -e "${GREEN}✓ Found CSS class 'btn-primary' (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ CSS class 'btn-primary' not found${NC}"
    exit 1
fi

# Test CSS ID
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":12,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"header-container\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "header-container" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "header-container" && echo "$CLI_RESPONSE" | grep -q "header-container"; then
    echo -e "${GREEN}✓ Found CSS id 'header-container' (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ CSS id 'header-container' not found${NC}"
    exit 1
fi

# Test 8: Search for SCSS classes (uses card selector from test_styles.scss)
echo ""
echo "Test 8: Searching for SCSS classes in test_styles.scss..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":14,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"responsive-grid\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "responsive-grid" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "responsive-grid" && echo "$CLI_RESPONSE" | grep -q "responsive-grid"; then
    echo -e "${GREEN}✓ Found SCSS class 'responsive-grid' (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ SCSS class 'responsive-grid' not found${NC}"
    exit 1
fi

# Test 9: Phase 4 - Search for CSS class referenced in JavaScript (hybrid web ecosystem)
echo ""
echo "Test 9: Searching for CSS class 'btn-primary' referenced in spa_app.js..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":15,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"btn-primary\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "btn-primary" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "btn-primary" && echo "$CLI_RESPONSE" | grep -q "btn-primary"; then
    echo -e "${GREEN}✓ Found CSS class 'btn-primary' (used in HTML, defined in CSS, referenced in JS, MCP & CLI)${NC}"
else
    echo -e "${RED}✗ CSS class 'btn-primary' not found in hybrid search${NC}"
    exit 1
fi

# Test 10: Phase 4 - Search for HTML element ID referenced in JavaScript
echo ""
echo "Test 10: Searching for HTML id 'app-container' referenced in spa_app.js..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":16,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"app-container\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "app-container" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "app-container" && echo "$CLI_RESPONSE" | grep -q "app-container"; then
    echo -e "${GREEN}✓ Found HTML id 'app-container' (defined in HTML, manipulated in JS, MCP & CLI)${NC}"
else
    echo -e "${RED}✗ HTML id 'app-container' not found in hybrid search${NC}"
    exit 1
fi

# Test 11: Phase 4 - Search for dashboard element referenced in JavaScript
echo ""
echo "Test 11: Searching for HTML id 'dashboard' referenced in spa_app.js..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":17,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"dashboard\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "dashboard" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "dashboard" && echo "$CLI_RESPONSE" | grep -q "dashboard"; then
    echo -e "${GREEN}✓ Found HTML id 'dashboard' (defined in HTML, manipulated in JS, MCP & CLI)${NC}"
else
    echo -e "${RED}✗ HTML id 'dashboard' not found in hybrid search${NC}"
    exit 1
fi

# Test 12: Phase 4 - Search for toggle button element
echo ""
echo "Test 12: Searching for HTML id 'toggle-btn' used in theme switching..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":18,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"toggle-btn\",\"repo_name\":\"$REPO_NAME\"}}}"
MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "toggle-btn" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "toggle-btn" && echo "$CLI_RESPONSE" | grep -q "toggle-btn"; then
    echo -e "${GREEN}✓ Found HTML id 'toggle-btn' (cross-language reference, MCP & CLI)${NC}"
else
    echo -e "${RED}✗ HTML id 'toggle-btn' not found${NC}"
    exit 1
fi

# Test 13: Kotlin support - Explore Kotlin file
echo ""
echo "Test 13: Exploring sample.kt for Kotlin class extraction (via MCP and CLI)..."
KT_FILE="$TEST_FILES_DIR/sample.kt"
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":19,\"method\":\"tools/call\",\"params\":{\"name\":\"explore_file\",\"arguments\":{\"file_path\":\"$KT_FILE\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- explore "$KT_FILE" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "UserService" && echo "$CLI_RESPONSE" | grep -q "UserService"; then
    echo -e "${GREEN}✓ Found Kotlin class UserService (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ Kotlin class UserService not found${NC}"
    exit 1
fi

# Test 14: Kotlin interface extraction
echo ""
echo "Test 14: Searching for Kotlin interface Repository..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":20,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"Repository\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "Repository" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "Repository" && echo "$CLI_RESPONSE" | grep -q "Repository"; then
    echo -e "${GREEN}✓ Found (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ Not found${NC}"
    exit 1
fi

# Test 15: Kotlin object (singleton) extraction
echo ""
echo "Test 15: Searching for Kotlin singleton object DatabaseManager..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":21,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"DatabaseManager\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "DatabaseManager" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "DatabaseManager" && echo "$CLI_RESPONSE" | grep -q "DatabaseManager"; then
    echo -e "${GREEN}✓ Found (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ Not found${NC}"
    exit 1
fi

# Test 16: Kotlin data class extraction
echo ""
echo "Test 16: Searching for Kotlin data class User..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":22,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"User\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "User" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "User" && echo "$CLI_RESPONSE" | grep -q "User"; then
    echo -e "${GREEN}✓ Found (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ Not found${NC}"
    exit 1
fi

# Test 17: Kotlin companion object extraction via explore_file
echo ""
echo "Test 17: Exploring sample.kt for ConfigManager with companion object..."
KT_FILE="$TEST_FILES_DIR/sample.kt"
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":23,\"method\":\"tools/call\",\"params\":{\"name\":\"explore_file\",\"arguments\":{\"file_path\":\"$KT_FILE\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- explore "$KT_FILE" -r "$REPO_NAME" -o markdown 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "ConfigManager" && echo "$CLI_RESPONSE" | grep -q "ConfigManager"; then
    echo -e "${GREEN}✓ Found ConfigManager in sample.kt (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ Not found${NC}"
    exit 1
fi

# Test 18: Kotlin function extraction
echo ""
echo "Test 18: Searching for top-level Kotlin function greetUser..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":24,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"greetUser\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "greetUser" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "greetUser" && echo "$CLI_RESPONSE" | grep -q "greetUser"; then
    echo -e "${GREEN}✓ Found (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ Not found${NC}"
    exit 1
fi

# Test 19: Kotlin extension function extraction
echo ""
echo "Test 19: Searching for Kotlin extension function isValidEmail..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":25,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"isValidEmail\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "isValidEmail" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "isValidEmail" && echo "$CLI_RESPONSE" | grep -q "isValidEmail"; then
    echo -e "${GREEN}✓ Found Kotlin extension function isValidEmail (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ Kotlin extension function isValidEmail not found${NC}"
    exit 1
fi

# Test 20: Kotlin annotation extraction
echo ""
echo "Test 20: Searching for @Service annotation in UserService..."
MCP_REQUEST='{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"explore_file","arguments":{"file_path":"'"$KT_FILE"'"}}}'

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- explore "$KT_FILE" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "Service" && echo "$CLI_RESPONSE" | grep -q "Service"; then
    echo -e "${GREEN}✓ Found Kotlin @Service annotation (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ Kotlin @Service annotation not found${NC}"
    exit 1
fi

# Test 21: Kotlin method call tracking
echo ""
echo "Test 21: Searching for UserRepository find operations (via MCP and CLI)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":27,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"findById\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "findById" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "findById" || echo "$CLI_RESPONSE" | grep -q "findById"; then
    echo -e "${GREEN}✓ Found Kotlin method findById and its callers (MCP or CLI)${NC}"
else
    echo -e "${YELLOW}  Note: This is expected if method call tracking for Kotlin needs tuning${NC}"
fi


# Test 22: Java field_access and FQN resolution for method calls
echo ""
echo "Test 22: Searching for callers of ChatMemory.add (via MCP and CLI)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":28,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"ChatMemory.add\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "ChatMemory.add" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "before" || echo "$CLI_RESPONSE" | grep -q "before"; then
    echo -e "${GREEN}✓ Found ChatMemoryAdvisor.before calling ChatMemory.add (MCP or CLI)${NC}"
else
    echo -e "${RED}✗ ChatMemoryAdvisor.before calling ChatMemory.add not found${NC}"
    exit 1
fi

# Test 23: Java method signature search with parameter types
echo ""
echo "Test 23: Searching for Java method by full signature registerUser(String... (via MCP and CLI)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":29,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"registerUser(String\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "registerUser(String" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "createUser" || echo "$CLI_RESPONSE" | grep -q "createUser"; then
    echo -e "${GREEN}✓ Found callers of registerUser by signature (MCP or CLI)${NC}"
else
    echo -e "${RED}✗ Signature-based search for registerUser failed${NC}"
    exit 1
fi

# Test 24: Kotlin method signature search with parameter type
echo ""
echo "Test 24: Searching for Kotlin method by signature fragment : Int... (via MCP and CLI)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":30,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\": Int\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers ": Int" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "loadData\|UserService" || echo "$CLI_RESPONSE" | grep -q "loadData\|UserService"; then
    echo -e "${GREEN}✓ Found Kotlin callers by signature fragment (MCP or CLI)${NC}"
else
    echo -e "${RED}✗ Kotlin callers by signature fragment not found${NC}"
    exit 1
fi

# Test 25: TypeScript method signature search with parameter type
echo ""
echo "Test 25: Searching for TypeScript method by parameter type EventData... (via MCP and CLI)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":31,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"EventData\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "EventData" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "trackEvent" || echo "$CLI_RESPONSE" | grep -q "trackEvent"; then
    echo -e "${GREEN}✓ Found TypeScript methods using EventData type (MCP or CLI)${NC}"
else
    echo -e "${RED}✗ TypeScript methods using EventData type not found${NC}"
    exit 1
fi

# Test 26: Java inheritance — IMPLEMENTS edge (normal class → interface)
echo ""
echo "Test 26: Verifying Java IMPLEMENTS edge for LoggingHandler → MessageHandler..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":32,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"MessageHandler\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "MessageHandler" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "LoggingHandler" || echo "$CLI_RESPONSE" | grep -q "LoggingHandler"; then
    echo -e "${GREEN}✓ MCP: Found IMPLEMENTS edge LoggingHandler → MessageHandler${NC}"
else
    echo -e "${RED}✗ MCP: Missing IMPLEMENTS edge LoggingHandler → MessageHandler${NC}"
    exit 1
fi

# Test 27: Java inheritance — IMPLEMENTS edge (UserRepository → Repository)
echo ""
echo "Test 27: Verifying Java IMPLEMENTS edge for UserRepository → Repository..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":33,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"Repository\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "Repository" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "UserRepository" || echo "$CLI_RESPONSE" | grep -q "UserRepository"; then
    echo -e "${GREEN}✓ MCP: Found IMPLEMENTS edge UserRepository → Repository${NC}"
else
    echo -e "${RED}✗ MCP: Missing IMPLEMENTS edge UserRepository → Repository${NC}"
    exit 1
fi

# Test 28: Java inheritance — EXTENDS edge (AdminUser → User)
echo ""
echo "Test 28: Verifying Java EXTENDS edge for AdminUser → User..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":34,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"User\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "User" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "AdminUser" || echo "$CLI_RESPONSE" | grep -q "AdminUser"; then
    echo -e "${GREEN}✓ MCP: Found EXTENDS edge AdminUser → User${NC}"
else
    echo -e "${RED}✗ MCP: Missing EXTENDS edge AdminUser → User${NC}"
    exit 1
fi

# Test 29: Java interface extending interface (AuditableRepository → Repository)
echo ""
echo "Test 29: Verifying Java interface EXTENDS edge AuditableRepository → Repository..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":35,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"Repository\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "Repository" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "AuditableRepository" || echo "$CLI_RESPONSE" | grep -q "AuditableRepository"; then
    echo -e "${GREEN}✓ MCP: Found EXTENDS edge AuditableRepository → Repository${NC}"
else
    echo -e "${RED}✗ MCP: Missing EXTENDS edge AuditableRepository → Repository${NC}"
    exit 1
fi

# Test 30: Java package FQN resolution
echo ""
echo "Test 30: Verifying Java FQN includes package name..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":36,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"UserService\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
if echo "$MCP_RESPONSE" | grep -q "com.example.knot.test.UserService"; then
    echo -e "${GREEN}✓ MCP: FQN includes package prefix com.example.knot.test${NC}"
else
    echo -e "${RED}✗ MCP: FQN missing package prefix${NC}"
    exit 1
fi

# Test 31: Java anonymous class implements interface
echo ""
echo "Test 31: Verifying anonymous class implementing interface (EventBroadcaster calls handle on MessageHandler)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":37,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"handle\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "handle" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "sendMessage" || echo "$CLI_RESPONSE" | grep -q "sendMessage"; then
    echo -e "${GREEN}✓ MCP: Found caller sendMessage invoking handle on anonymous MessageHandler${NC}"
else
    echo -e "${RED}✗ MCP: Anonymous class handle() invocation not tracked${NC}"
    exit 1
fi

# Test 32: Java search_hybrid_context finds MessageHandler interface
echo ""
echo "Test 32: Verifying MessageHandler interface is searchable..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":38,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"MessageHandler\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
if echo "$MCP_RESPONSE" | grep -q "MessageHandler"; then
    echo -e "${GREEN}✓ MCP: MessageHandler interface found in search results${NC}"
else
    echo -e "${RED}✗ MCP: MessageHandler interface not found in search${NC}"
    exit 1
fi

# Test 33: TypeScript class EXTENDS (CacheService → BaseService)
echo ""
echo "Test 33: Verifying TypeScript EXTENDS edge CacheService → BaseService..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":39,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"BaseService\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "BaseService" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "CacheService" || echo "$CLI_RESPONSE" | grep -q "CacheService"; then
    echo -e "${GREEN}✓ MCP: Found EXTENDS edge CacheService → BaseService${NC}"
else
    echo -e "${RED}✗ MCP: Missing EXTENDS edge CacheService → BaseService${NC}"
    exit 1
fi

# Test 34: TypeScript class IMPLEMENTS (CacheService → IStorage)
echo ""
echo "Test 34: Verifying TypeScript IMPLEMENTS edge CacheService → IStorage..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":40,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"IStorage\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "IStorage" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "CacheService" || echo "$CLI_RESPONSE" | grep -q "CacheService"; then
    echo -e "${GREEN}✓ MCP: Found IMPLEMENTS edge CacheService → IStorage${NC}"
else
    echo -e "${RED}✗ MCP: Missing IMPLEMENTS edge CacheService → IStorage${NC}"
    exit 1
fi

# Test 35: TypeScript interface EXTENDS (ICache → IStorage)
echo ""
echo "Test 35: Verifying TypeScript interface EXTENDS edge ICache → IStorage..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":41,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"IStorage\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "IStorage" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "ICache" || echo "$CLI_RESPONSE" | grep -q "ICache"; then
    echo -e "${GREEN}✓ MCP: Found EXTENDS edge ICache → IStorage (interface inheritance)${NC}"
else
    echo -e "${RED}✗ MCP: Missing EXTENDS edge ICache → IStorage${NC}"
    exit 1
fi

# Test 36: TypeScript top-level function type references (processPayload → IPayload)
echo ""
echo "Test 36: Verifying TypeScript top-level function type reference processPayload → IPayload..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":42,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"IPayload\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "IPayload" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "processPayload" || echo "$CLI_RESPONSE" | grep -q "processPayload"; then
    echo -e "${GREEN}✓ MCP: Found top-level function processPayload using IPayload (type reference)${NC}"
else
    echo -e "${RED}✗ MCP: Missing type reference processPayload → IPayload${NC}"
    exit 1
fi

# Test 37: Prefix search boost (partial name query should find exact match first)
echo ""
echo "Test 37: Verifying prefix search returns exact name match first..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":43,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"ExternalAcc\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- search "ExternalAcc" 2>/dev/null)

# Check that a prefix query like "IPa" finds IPayload (from test_typescript.ts fixtures)
MCP_REQUEST2="{\"jsonrpc\":\"2.0\",\"id\":44,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"IPa\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE2=$(echo "$MCP_REQUEST2" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE2=$(cargo run --release --bin knot -- search "IPa" 2>/dev/null)

if echo "$MCP_RESPONSE2" | grep -q "IPayload" || echo "$CLI_RESPONSE2" | grep -q "IPayload"; then
    echo -e "${GREEN}✓ MCP: Prefix search for 'IPa' returned IPayload (name prefix boost works)${NC}"
else
    echo -e "${RED}✗ MCP: Prefix search for 'IPa' did not return IPayload${NC}"
    exit 1
fi

# Test 38: TypeScript value reference (object literal VALUE uses Engine class)
echo ""
echo "Test 38: Verifying TypeScript value reference COMPONENT_REGISTRY → Engine..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":45,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"Engine\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "Engine" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "COMPONENT_REGISTRY" || echo "$CLI_RESPONSE" | grep -q "COMPONENT_REGISTRY"; then
    echo -e "${GREEN}✓ MCP: Found value reference COMPONENT_REGISTRY → Engine${NC}"
else
    echo -e "${RED}✗ MCP: Missing value reference COMPONENT_REGISTRY → Engine${NC}"
    exit 1
fi

# Test 39: JavaScript cross-file alias resolution (require)
echo ""
echo "Test 39: Verifying JavaScript cross-file alias resolution (MyJsAlias → MyJsTarget)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":46,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"MyJsTarget\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "MyJsTarget" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "callerJs" || echo "$CLI_RESPONSE" | grep -q "callerJs"; then
    echo -e "${GREEN}✓ MCP: Found cross-file alias resolution callerJs → MyJsTarget${NC}"
else
    echo -e "${RED}✗ MCP: Missing alias resolution callerJs → MyJsTarget${NC}"
    exit 1
fi

# Test 40: TypeScript cross-file alias resolution (import)
echo ""
echo "Test 40: Verifying TypeScript cross-file alias resolution (MyTsAlias → MyTsTarget)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":47,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"MyTsTarget\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "MyTsTarget" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "callerTs" || echo "$CLI_RESPONSE" | grep -q "callerTs"; then
    echo -e "${GREEN}✓ MCP: Found cross-file alias resolution callerTs → MyTsTarget${NC}"
else
    echo -e "${RED}✗ MCP: Missing alias resolution callerTs → MyTsTarget${NC}"
    exit 1
fi

# Test 41: JS circular require alias resolution — completes without hanging
echo ""
echo "Test 41: Verifying JS circular require alias resolution (CycleB ⇄ CycleA)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":48,\"method\":\"tools/call\",\"params\":{\"name\":\"search_hybrid_context\",\"arguments\":{\"query\":\"CycleA_target\",\"repo_name\":\"$REPO_NAME\",\"max_results\":5}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "CycleA_target" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "CycleA_target" || echo "$CLI_RESPONSE" | grep -q "CycleA_target"; then
    echo -e "${GREEN}✓ MCP: Circular require aliases resolved without deadlock; CycleA_target found${NC}"
else
    echo -e "${RED}✗ MCP: Missing CycleA_target after circular alias resolution${NC}"
    exit 1
fi

# Test 42: JS circular require — cross-file caller relationship preserved
echo ""
echo "Test 42: Verifying JS circular require preserves relationships (callerInB → CycleA_target)..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":49,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"CycleA_target\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "CycleA_target" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "callerInB" || echo "$CLI_RESPONSE" | grep -q "callerInB"; then
    echo -e "${GREEN}✓ MCP: callerInB → CycleA_target relationship preserved across circular alias${NC}"
else
    echo -e "${RED}✗ MCP: Missing callerInB → CycleA_target relationship${NC}"
    exit 1
fi

# Test 43: TypeScript import capture — find_callers for TsImportFoo
echo ""
echo "Test 43: Verifying TypeScript import capture — find_callers for TsImportFoo..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":50,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"TsImportFoo\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "TsImportFoo" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "ts_imports_uses.ts" || echo "$CLI_RESPONSE" | grep -q "ts_imports_uses.ts"; then
    echo -e "${GREEN}✓ Found ts_imports_uses.ts as caller of TsImportFoo via import (MCP or CLI)${NC}"
else
    echo -e "${RED}✗ ts_imports_uses.ts not found as caller of TsImportFoo${NC}"
    exit 1
fi

# Test 44: TypeScript import capture — find_callers for TsImportQux (not alias TsImportBar)
echo ""
echo "Test 44: Verifying TypeScript import-as capture — find_callers for TsImportQux..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":51,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"TsImportQux\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "TsImportQux" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "ts_imports_uses.ts" || echo "$CLI_RESPONSE" | grep -q "ts_imports_uses.ts"; then
    echo -e "${GREEN}✓ Found ts_imports_uses.ts as caller of TsImportQux via import-as (MCP or CLI)${NC}"
else
    echo -e "${RED}✗ ts_imports_uses.ts not found as caller of TsImportQux${NC}"
    exit 1
fi

# Test 44b: Verify alias TsImportBar does NOT appear as having callers
echo ""
echo "Test 44b: Verifying TypeScript alias TsImportBar does NOT have callers..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":52,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"TsImportBar\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)

if echo "$MCP_RESPONSE" | grep -q "ts_imports_uses.ts"; then
    echo -e "${RED}✗ Alias TsImportBar should NOT have callers (alias should resolve to TsImportQux)${NC}"
    exit 1
else
    echo -e "${GREEN}✓ Alias TsImportBar correctly has no callers (MCP)${NC}"
fi

# Test 45: TypeScript explore_file on ts_imports_uses.ts — verify Imports section
echo ""
echo "Test 45: Verifying explore_file on ts_imports_uses.ts shows Imports / Referenced Types..."
TS_IMPORTS_FILE="$TEST_FILES_DIR/ts_imports_uses.ts"
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":53,\"method\":\"tools/call\",\"params\":{\"name\":\"explore_file\",\"arguments\":{\"file_path\":\"$TS_IMPORTS_FILE\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- explore "$TS_IMPORTS_FILE" -r "$REPO_NAME" -o markdown 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "## Imports / Referenced Types" && echo "$CLI_RESPONSE" | grep -q "## Imports / Referenced Types"; then
    echo -e "${GREEN}✓ explore_file shows Imports / Referenced Types section (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ explore_file missing Imports / Referenced Types section${NC}"
    exit 1
fi

if (echo "$MCP_RESPONSE" | grep -q "TsImportFoo") && (echo "$MCP_RESPONSE" | grep -q "TsImportQux"); then
    echo -e "${GREEN}✓ Imports section lists TsImportFoo and TsImportQux (MCP)${NC}"
else
    echo -e "${RED}✗ Imports section missing TsImportFoo or TsImportQux${NC}"
    exit 1
fi

# Test 46: JavaScript import capture — find_callers for JsImportFoo
echo ""
echo "Test 46: Verifying JavaScript import capture — find_callers for JsImportFoo..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":54,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"JsImportFoo\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "JsImportFoo" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "js_imports_uses.js" || echo "$CLI_RESPONSE" | grep -q "js_imports_uses.js"; then
    echo -e "${GREEN}✓ Found js_imports_uses.js as caller of JsImportFoo via import (MCP or CLI)${NC}"
else
    echo -e "${RED}✗ js_imports_uses.js not found as caller of JsImportFoo${NC}"
    exit 1
fi

# Test 47: JavaScript require destructuring — find_callers for JsImportQux
echo ""
echo "Test 47: Verifying JavaScript require destructuring — find_callers for JsImportQux..."
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":55,\"method\":\"tools/call\",\"params\":{\"name\":\"find_callers\",\"arguments\":{\"entity_name\":\"JsImportQux\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- callers "JsImportQux" 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "js_imports_uses.js" || echo "$CLI_RESPONSE" | grep -q "js_imports_uses.js"; then
    echo -e "${GREEN}✓ Found js_imports_uses.js as caller of JsImportQux via require destructuring (MCP or CLI)${NC}"
else
    echo -e "${RED}✗ js_imports_uses.js not found as caller of JsImportQux${NC}"
    exit 1
fi

# Test 48: JavaScript explore_file on js_imports_uses.js — verify Imports section
echo ""
echo "Test 48: Verifying explore_file on js_imports_uses.js shows Imports / Referenced Types..."
JS_IMPORTS_FILE="$TEST_FILES_DIR/js_imports_uses.js"
MCP_REQUEST="{\"jsonrpc\":\"2.0\",\"id\":56,\"method\":\"tools/call\",\"params\":{\"name\":\"explore_file\",\"arguments\":{\"file_path\":\"$JS_IMPORTS_FILE\",\"repo_name\":\"$REPO_NAME\"}}}"

MCP_RESPONSE=$(echo "$MCP_REQUEST" | cargo run --release --bin knot-mcp 2>/dev/null | tail -n 1)
CLI_RESPONSE=$(cargo run --release --bin knot -- explore "$JS_IMPORTS_FILE" -r "$REPO_NAME" -o markdown 2>/dev/null)

if echo "$MCP_RESPONSE" | grep -q "## Imports / Referenced Types" && echo "$CLI_RESPONSE" | grep -q "## Imports / Referenced Types"; then
    echo -e "${GREEN}✓ explore_file shows Imports / Referenced Types section for JS (MCP & CLI)${NC}"
else
    echo -e "${RED}✗ explore_file missing Imports / Referenced Types section for JS${NC}"
    exit 1
fi

if (echo "$MCP_RESPONSE" | grep -q "JsImportFoo") && (echo "$MCP_RESPONSE" | grep -q "JsImportQux"); then
    echo -e "${GREEN}✓ JS Imports section lists JsImportFoo and JsImportQux (MCP)${NC}"
else
    echo -e "${RED}✗ JS Imports section missing JsImportFoo or JsImportQux${NC}"
    exit 1
fi

# Step 5: Success
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}All E2E tests passed! ✓${NC}"
echo -e "${GREEN}Validated both knot-mcp and knot CLI${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo "Validated features:"
echo "  ✓ TypeScript decorator extraction (@Component, @NgModule)"
echo "  ✓ Type reference extraction (constructor DI)"
echo "  ✓ TypeScript inheritance extraction (EXTENDS + IMPLEMENTS)"
echo "  ✓ TypeScript interface inheritance (interface extends interface)"
echo "  ✓ JavaScript class parsing and JSX components"
echo "  ✓ Java annotation extraction"
echo "  ✓ Java field_access method invocations and FQN-based caller resolution"
echo "  ✓ Java inheritance extraction (EXTENDS + IMPLEMENTS)"
echo "  ✓ Java interface inheritance (interface extends interface)"
echo "  ✓ Java package declaration parsing and FQN prefixing"
echo "  ✓ Java anonymous class interface implementation"
echo "  ✓ HTML custom elements extraction (Angular components)"
echo "  ✓ HTML id and class attributes indexing"
echo "  ✓ JSX id and className attributes indexing"
echo "  ✓ CSS class and id selector extraction"
echo "  ✓ CSS Custom Properties (variables) indexing"
echo "  ✓ SCSS variable definitions extraction"
echo "  ✓ SCSS mixin and function extraction"
echo "  ✓ MCP server query functionality"
echo "  ✓ Phase 4: Hybrid Web Ecosystem - Cross-language CSS class references (JS → CSS)"
echo "  ✓ Phase 4: Hybrid Web Ecosystem - Cross-language HTML ID references (JS → HTML)"
echo "  ✓ Phase 4: Hybrid Web Ecosystem - Multi-file SPA linking (HTML → JS, HTML → CSS)"
echo "  ✓ Phase 5: Kotlin Support - Class, interface, and object extraction"
echo "  ✓ Phase 5: Kotlin Support - Data class extraction"
echo "  ✓ Phase 5: Kotlin Support - Companion object extraction"
echo "  ✓ Phase 5: Kotlin Support - Top-level function extraction"
echo "  ✓ Phase 5: Kotlin Support - Extension function extraction"
echo "  ✓ Phase 5: Kotlin Support - Annotation extraction"
echo "  ✓ Phase 6: Signature-based search - Java method signatures"
echo "  ✓ Phase 6: Signature-based search - Kotlin parameter types"
echo "  ✓ Phase 6: Signature-based search - TypeScript type annotations"
echo "  ✓ Phase 7: Prefix name search boost (exact-name matches first)"
echo "  ✓ Phase 8: TypeScript value references (capitalized identifiers in object literals/arrays)"
echo "  ✓ Phase 9: Cross-file alias resolution (require/import)"
echo "  ✓ Phase 10: Circular alias cycle resolution (no deadlock + relationships preserved)"
echo "  ✓ Phase 11: TypeScript import capture (named imports, import-as aliases)"
echo "  ✓ Phase 11: JavaScript import/require capture (import, require destructuring)"
echo "  ✓ Phase 11: Explore file outgoing references (Imports / Referenced Types section)"
echo ""

exit 0
