/**
 * End-to-end integration tests for the mcpctl 3-tier architecture.
 *
 * These tests wire together the real McpRouter, McpdUpstream, LlmProcessor,
 * TieredHealthMonitor, and discovery logic against a mock mcpd HTTP server
 * (node:http) and a mock LLM provider. No Docker or external services needed.
 */

import { describe, it, expect, vi, beforeEach, afterEach, afterAll } from 'vitest';
import { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http';

import { McpRouter } from '../../src/router.js';
import { McpdUpstream } from '../../src/upstream/mcpd.js';
import { McpdClient } from '../../src/http/mcpd-client.js';
import { LlmProcessor, DEFAULT_PROCESSOR_CONFIG } from '../../src/llm/processor.js';
import { ResponsePaginator } from '../../src/llm/pagination.js';
import { ProviderRegistry } from '../../src/providers/registry.js';
import { TieredHealthMonitor } from '../../src/health/tiered.js';
import { refreshUpstreams } from '../../src/discovery.js';
import type { LlmProvider, CompletionResult, CompletionOptions } from '../../src/providers/types.js';
import type { JsonRpcRequest } from '../../src/types.js';

// ---------------------------------------------------------------------------
// Mock mcpd HTTP server
// ---------------------------------------------------------------------------

interface MockMcpdServerConfig {
  /** MCP servers that mcpd reports */
  servers: Array<{ id: string; name: string; transport: string; status?: string | undefined }>;
  /** Map of "serverId:method" -> response payload */
  proxyResponses: Map<string, { result?: unknown; error?: { code: number; message: string } }>;
  /** Instances returned by /api/v1/instances */
  instances: Array<{ name: string; status: string }>;
  /** The expected auth token (all requests must carry this) */
  expectedToken: string;
  /** Track requests for assertion purposes */
  requestLog: Array<{
    method: string;
    url: string;
    authHeader: string | undefined;
    body: unknown;
  }>;
}

function defaultMockConfig(): MockMcpdServerConfig {
  return {
    servers: [],
    proxyResponses: new Map(),
    instances: [],
    expectedToken: 'test-token-12345',
    requestLog: [],
  };
}

/**
 * Starts a real HTTP server on an ephemeral port that mimics mcpd's API.
 * Returns the server, its base URL, and the config (mutable for per-test tweaks).
 */
async function startMockMcpd(
  configOverrides?: Partial<MockMcpdServerConfig>,
): Promise<{ server: Server; baseUrl: string; config: MockMcpdServerConfig }> {
  const config: MockMcpdServerConfig = { ...defaultMockConfig(), ...configOverrides };

  const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
    // Collect body
    const chunks: Buffer[] = [];
    for await (const chunk of req) {
      chunks.push(chunk as Buffer);
    }
    const bodyStr = Buffer.concat(chunks).toString('utf-8');
    let body: unknown;
    try {
      body = bodyStr ? JSON.parse(bodyStr) : undefined;
    } catch {
      body = bodyStr;
    }

    // Log the request
    config.requestLog.push({
      method: req.method ?? 'GET',
      url: req.url ?? '/',
      authHeader: req.headers['authorization'],
      body,
    });

    // Auth check
    const auth = req.headers['authorization'];
    if (auth !== `Bearer ${config.expectedToken}`) {
      res.writeHead(401, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Unauthorized' }));
      return;
    }

    const url = req.url ?? '/';

    // Health endpoint
    if (url === '/health' && req.method === 'GET') {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ status: 'ok' }));
      return;
    }

    // Servers list
    if (url === '/api/v1/servers' && req.method === 'GET') {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(config.servers));
      return;
    }

    // Instances (TieredHealthMonitor uses /instances, other code may use /api/v1/instances)
    if ((url === '/instances' || url === '/api/v1/instances') && req.method === 'GET') {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ instances: config.instances }));
      return;
    }

    // MCP proxy endpoint
    if (url === '/api/v1/mcp/proxy' && req.method === 'POST') {
      const proxyReq = body as { serverId: string; method: string; params?: Record<string, unknown> };
      const key = `${proxyReq.serverId}:${proxyReq.method}`;
      const resp = config.proxyResponses.get(key);
      if (resp) {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(resp));
      } else {
        // Default: return a generic success
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ result: { ok: true } }));
      }
      return;
    }

    // Catch-all 404
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Not found' }));
  });

  // Listen on random port
  await new Promise<void>((resolve) => {
    server.listen(0, '127.0.0.1', resolve);
  });

  const addr = server.address();
  if (!addr || typeof addr === 'string') {
    throw new Error('Failed to get server address');
  }

  return {
    server,
    baseUrl: `http://127.0.0.1:${String(addr.port)}`,
    config,
  };
}

// ---------------------------------------------------------------------------
// Mock LLM provider
// ---------------------------------------------------------------------------

function createMockLlmProvider(
  name: string,
  handler: (options: CompletionOptions) => string,
): LlmProvider {
  return {
    name,
    async complete(options: CompletionOptions): Promise<CompletionResult> {
      const content = handler(options);
      return {
        content,
        toolCalls: [],
        usage: { promptTokens: 10, completionTokens: 5, totalTokens: 15 },
        finishReason: 'stop',
      };
    },
    async listModels() {
      return ['mock-model-1'];
    },
    async isAvailable() {
      return true;
    },
  };
}

function createFailingLlmProvider(name: string): LlmProvider {
  return {
    name,
    async complete(): Promise<CompletionResult> {
      throw new Error('LLM provider unavailable');
    },
    async listModels() {
      return [];
    },
    async isAvailable() {
      return false;
    },
  };
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe('End-to-end integration: 3-tier architecture', () => {
  let mockMcpd: { server: Server; baseUrl: string; config: MockMcpdServerConfig };
  let router: McpRouter;

  afterEach(async () => {
    if (router) {
      await router.closeAll();
    }
    if (mockMcpd?.server) {
      await new Promise<void>((resolve) => {
        mockMcpd.server.close(() => resolve());
      });
    }
  });

  // -----------------------------------------------------------------------
  // 1. Full tool call flow through McpdUpstream
  // -----------------------------------------------------------------------
  describe('Full tool call flow', () => {
    it('routes a tool call through McpRouter -> McpdUpstream -> mock mcpd and returns the response', async () => {
      mockMcpd = await startMockMcpd({
        servers: [
          { id: 'srv-slack', name: 'slack', transport: 'stdio' },
        ],
        proxyResponses: new Map([
          ['srv-slack:tools/list', {
            result: {
              tools: [
                { name: 'search_messages', description: 'Search Slack messages', inputSchema: {} },
                { name: 'send_message', description: 'Send a message', inputSchema: {} },
              ],
            },
          }],
          ['srv-slack:tools/call', {
            result: {
              content: [{ type: 'text', text: 'Found 3 messages matching "deploy"' }],
            },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();

      // Discover servers from mcpd and register them
      await refreshUpstreams(router, client);
      expect(router.getUpstreamNames()).toContain('slack');

      // Discover tools
      const tools = await router.discoverTools();
      expect(tools.map((t) => t.name)).toContain('slack/search_messages');
      expect(tools.map((t) => t.name)).toContain('slack/send_message');

      // Call a tool
      const response = await router.route({
        jsonrpc: '2.0',
        id: 'call-1',
        method: 'tools/call',
        params: { name: 'slack/search_messages', arguments: { query: 'deploy' } },
      });

      expect(response.error).toBeUndefined();
      expect(response.result).toEqual({
        content: [{ type: 'text', text: 'Found 3 messages matching "deploy"' }],
      });
    });

    it('handles tool call that returns an error from the upstream server', async () => {
      mockMcpd = await startMockMcpd({
        servers: [
          { id: 'srv-db', name: 'database', transport: 'stdio' },
        ],
        proxyResponses: new Map([
          ['srv-db:tools/list', {
            result: { tools: [{ name: 'query', description: 'Run SQL query' }] },
          }],
          ['srv-db:tools/call', {
            error: { code: -32000, message: 'Query timeout: exceeded 30s limit' },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      const response = await router.route({
        jsonrpc: '2.0',
        id: 'call-err',
        method: 'tools/call',
        params: { name: 'database/query', arguments: { sql: 'SELECT * FROM huge_table' } },
      });

      expect(response.error).toBeDefined();
      expect(response.error?.code).toBe(-32000);
      expect(response.error?.message).toContain('Query timeout');
    });
  });

  // -----------------------------------------------------------------------
  // 2. Tool call with LLM filtering
  // -----------------------------------------------------------------------
  describe('Tool call with LLM filtering', () => {
    it('filters a large response through the LLM processor and returns reduced data', async () => {
      // Generate a large response that exceeds the token threshold (~250 tokens = ~1000 chars)
      const largeItems = Array.from({ length: 100 }, (_, i) => ({
        id: i,
        name: `item-${String(i)}`,
        description: `This is a verbose description for item number ${String(i)} that adds bulk`,
        metadata: { created: '2025-01-01', tags: ['a', 'b', 'c'] },
      }));

      const filteredItems = [
        { id: 0, name: 'item-0' },
        { id: 1, name: 'item-1' },
        { id: 2, name: 'item-2' },
      ];

      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-data', name: 'dataserver', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-data:tools/list', {
            result: { tools: [{ name: 'search_records', description: 'Search records' }] },
          }],
          ['srv-data:tools/call', {
            result: { items: largeItems },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // Create LLM processor with mock provider that returns filtered data
      const registry = new ProviderRegistry();
      const mockProvider = createMockLlmProvider('test-filter', () => {
        return JSON.stringify({ items: filteredItems });
      });
      registry.register(mockProvider);

      const processor = new LlmProcessor(registry, {
        ...DEFAULT_PROCESSOR_CONFIG,
        enableFiltering: true,
        tokenThreshold: 50, // Low threshold so filtering kicks in
      });
      router.setLlmProcessor(processor);

      // Call the tool - should get filtered response
      const response = await router.route({
        jsonrpc: '2.0',
        id: 'filter-1',
        method: 'tools/call',
        params: { name: 'dataserver/search_records', arguments: { query: 'test' } },
      });

      expect(response.error).toBeUndefined();
      // The result should be the filtered (smaller) version
      const result = response.result as { items: Array<{ id: number; name: string }> };
      expect(result.items).toHaveLength(3);
      expect(result.items[0]?.name).toBe('item-0');
    });

    it('preserves original response size information in metrics', async () => {
      const largePayload = Array.from({ length: 80 }, (_, i) => ({
        id: i,
        value: 'x'.repeat(30),
      }));

      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-big', name: 'bigserver', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-big:tools/list', {
            result: { tools: [{ name: 'fetch_data', description: 'Fetch data' }] },
          }],
          ['srv-big:tools/call', {
            result: { data: largePayload },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      const registry = new ProviderRegistry();
      const mockProvider = createMockLlmProvider('test-metrics', () => {
        return JSON.stringify({ summary: 'reduced' });
      });
      registry.register(mockProvider);

      const processor = new LlmProcessor(registry, {
        ...DEFAULT_PROCESSOR_CONFIG,
        enableFiltering: true,
        tokenThreshold: 10,
      });
      router.setLlmProcessor(processor);

      await router.route({
        jsonrpc: '2.0',
        id: 'metrics-1',
        method: 'tools/call',
        params: { name: 'bigserver/fetch_data', arguments: {} },
      });

      const metrics = processor.getMetrics();
      expect(metrics.filterCount).toBe(1);
      expect(metrics.tokensSaved).toBeGreaterThan(0);
    });
  });

  // -----------------------------------------------------------------------
  // 3. Tool call without LLM (bypass)
  // -----------------------------------------------------------------------
  describe('Tool call without LLM (bypass)', () => {
    it('bypasses LLM filtering for simple create/delete operations', async () => {
      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-crud', name: 'crudserver', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-crud:tools/list', {
            result: {
              tools: [
                { name: 'create_record', description: 'Create a record' },
                { name: 'delete_record', description: 'Delete a record' },
                { name: 'search_records', description: 'Search records' },
              ],
            },
          }],
          ['srv-crud:tools/call', {
            result: { success: true, id: 'new-record-123' },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // Set up LLM processor - it should bypass create/delete ops
      const registry = new ProviderRegistry();
      let llmCallCount = 0;
      const mockProvider = createMockLlmProvider('test-bypass', () => {
        llmCallCount++;
        return '{}';
      });
      registry.register(mockProvider);

      const processor = new LlmProcessor(registry, {
        ...DEFAULT_PROCESSOR_CONFIG,
        enableFiltering: true,
        tokenThreshold: 0, // No threshold, always attempt filter
      });
      router.setLlmProcessor(processor);

      // Call create_record - should bypass LLM
      const createResponse = await router.route({
        jsonrpc: '2.0',
        id: 'bypass-1',
        method: 'tools/call',
        params: { name: 'crudserver/create_record', arguments: { name: 'test' } },
      });

      expect(createResponse.error).toBeUndefined();
      expect(createResponse.result).toEqual({ success: true, id: 'new-record-123' });
      expect(llmCallCount).toBe(0); // LLM was not called

      // Call delete_record - should also bypass
      const deleteResponse = await router.route({
        jsonrpc: '2.0',
        id: 'bypass-2',
        method: 'tools/call',
        params: { name: 'crudserver/delete_record', arguments: { id: 'record-1' } },
      });

      expect(deleteResponse.error).toBeUndefined();
      expect(llmCallCount).toBe(0); // Still not called
    });

    it('passes through directly when no LLM processor is set', async () => {
      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-simple', name: 'simpleserver', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-simple:tools/list', {
            result: { tools: [{ name: 'get_info', description: 'Get info' }] },
          }],
          ['srv-simple:tools/call', {
            result: { info: 'direct passthrough data' },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // No LLM processor set - direct passthrough
      const response = await router.route({
        jsonrpc: '2.0',
        id: 'direct-1',
        method: 'tools/call',
        params: { name: 'simpleserver/get_info', arguments: {} },
      });

      expect(response.error).toBeUndefined();
      expect(response.result).toEqual({ info: 'direct passthrough data' });
    });
  });

  // -----------------------------------------------------------------------
  // 4. Server discovery and routing
  // -----------------------------------------------------------------------
  describe('Server discovery and routing', () => {
    it('discovers tools from multiple servers and routes by name prefix', async () => {
      mockMcpd = await startMockMcpd({
        servers: [
          { id: 'srv-slack', name: 'slack', transport: 'stdio' },
          { id: 'srv-github', name: 'github', transport: 'stdio' },
        ],
        proxyResponses: new Map([
          ['srv-slack:tools/list', {
            result: {
              tools: [
                { name: 'search_messages', description: 'Search messages' },
                { name: 'post_message', description: 'Post a message' },
              ],
            },
          }],
          ['srv-github:tools/list', {
            result: {
              tools: [
                { name: 'list_issues', description: 'List issues' },
                { name: 'search_code', description: 'Search code' },
              ],
            },
          }],
          ['srv-slack:tools/call', {
            result: { source: 'slack', data: 'slack response' },
          }],
          ['srv-github:tools/call', {
            result: { source: 'github', data: 'github response' },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);

      // Discover all tools
      const tools = await router.discoverTools();
      expect(tools).toHaveLength(4);
      expect(tools.map((t) => t.name).sort()).toEqual([
        'github/list_issues',
        'github/search_code',
        'slack/post_message',
        'slack/search_messages',
      ]);

      // Route to slack
      const slackResponse = await router.route({
        jsonrpc: '2.0',
        id: 'multi-1',
        method: 'tools/call',
        params: { name: 'slack/search_messages', arguments: { query: 'hello' } },
      });
      expect(slackResponse.error).toBeUndefined();
      expect((slackResponse.result as Record<string, unknown>)['source']).toBe('slack');

      // Route to github
      const githubResponse = await router.route({
        jsonrpc: '2.0',
        id: 'multi-2',
        method: 'tools/call',
        params: { name: 'github/list_issues', arguments: { repo: 'test' } },
      });
      expect(githubResponse.error).toBeUndefined();
      expect((githubResponse.result as Record<string, unknown>)['source']).toBe('github');
    });

    it('refreshUpstreams removes stale servers and adds new ones', async () => {
      mockMcpd = await startMockMcpd({
        servers: [
          { id: 'srv-a', name: 'server-a', transport: 'stdio' },
          { id: 'srv-b', name: 'server-b', transport: 'stdio' },
        ],
        proxyResponses: new Map(),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();

      // First discovery
      const first = await refreshUpstreams(router, client);
      expect(first.sort()).toEqual(['server-a', 'server-b']);
      expect(router.getUpstreamNames().sort()).toEqual(['server-a', 'server-b']);

      // Reconfigure mock: remove server-a, add server-c
      mockMcpd.config.servers = [
        { id: 'srv-b', name: 'server-b', transport: 'stdio' },
        { id: 'srv-c', name: 'server-c', transport: 'stdio' },
      ];

      // Second discovery
      const second = await refreshUpstreams(router, client);
      expect(second.sort()).toEqual(['server-b', 'server-c']);
      expect(router.getUpstreamNames().sort()).toEqual(['server-b', 'server-c']);
    });

    it('uses the initialize method to return proxy capabilities', async () => {
      router = new McpRouter();

      const response = await router.route({
        jsonrpc: '2.0',
        id: 'init-1',
        method: 'initialize',
      });

      expect(response.error).toBeUndefined();
      const result = response.result as Record<string, unknown>;
      expect(result['protocolVersion']).toBe('2024-11-05');
      expect((result['serverInfo'] as Record<string, unknown>)['name']).toBe('mcpctl-proxy');
      const capabilities = result['capabilities'] as Record<string, unknown>;
      expect(capabilities).toHaveProperty('tools');
      expect(capabilities).toHaveProperty('resources');
      expect(capabilities).toHaveProperty('prompts');
    });
  });

  // -----------------------------------------------------------------------
  // 5. Error handling
  // -----------------------------------------------------------------------
  describe('Error handling', () => {
    it('returns graceful error when mcpd is unreachable', async () => {
      // Create a client pointing to a port that nothing listens on
      const client = new McpdClient('http://127.0.0.1:1', 'some-token');
      router = new McpRouter();

      // refreshUpstreams will fail because mcpd is unreachable
      await expect(refreshUpstreams(router, client)).rejects.toThrow();
    });

    it('returns error response when mcpd proxy call fails mid-request', async () => {
      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-flaky', name: 'flaky', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-flaky:tools/list', {
            result: { tools: [{ name: 'do_thing', description: 'Do a thing' }] },
          }],
          // No response configured for tools/call -> will get default { result: { ok: true } }
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // Close the mock server to simulate mcpd going down
      await new Promise<void>((resolve) => {
        mockMcpd.server.close(() => resolve());
      });

      // Now try to call - mcpd is down
      const response = await router.route({
        jsonrpc: '2.0',
        id: 'err-1',
        method: 'tools/call',
        params: { name: 'flaky/do_thing', arguments: {} },
      });

      expect(response.error).toBeDefined();
      expect(response.error?.message).toContain('mcpd proxy error');
    });

    it('falls back to unfiltered response when LLM fails', async () => {
      const largePayload = Array.from({ length: 60 }, (_, i) => ({
        id: i,
        data: 'x'.repeat(30),
      }));

      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-fallback', name: 'fallbackserver', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-fallback:tools/list', {
            result: { tools: [{ name: 'get_data', description: 'Get data' }] },
          }],
          ['srv-fallback:tools/call', {
            result: { items: largePayload },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // Use a failing LLM provider
      const registry = new ProviderRegistry();
      registry.register(createFailingLlmProvider('failing-llm'));

      const processor = new LlmProcessor(registry, {
        ...DEFAULT_PROCESSOR_CONFIG,
        enableFiltering: true,
        tokenThreshold: 10, // Low threshold to trigger filtering
      });
      router.setLlmProcessor(processor);

      // Call - LLM will fail, should fall back to original response
      const response = await router.route({
        jsonrpc: '2.0',
        id: 'fallback-1',
        method: 'tools/call',
        params: { name: 'fallbackserver/get_data', arguments: {} },
      });

      expect(response.error).toBeUndefined();
      // Should still get the original (unfiltered) response
      const result = response.result as { items: Array<{ id: number; data: string }> };
      expect(result.items).toHaveLength(60);
      expect(result.items[0]?.data).toBe('x'.repeat(30));
    });

    it('returns proper error for unknown tool name', async () => {
      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-known', name: 'knownserver', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-known:tools/list', {
            result: { tools: [{ name: 'real_tool', description: 'Real' }] },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // Try to call a tool that doesn't exist
      const response = await router.route({
        jsonrpc: '2.0',
        id: 'unknown-1',
        method: 'tools/call',
        params: { name: 'knownserver/nonexistent_tool', arguments: {} },
      });

      expect(response.error).toBeDefined();
      expect(response.error?.code).toBe(-32601);
      expect(response.error?.message).toContain('Unknown');
    });

    it('returns error for completely unknown server prefix', async () => {
      router = new McpRouter();

      const response = await router.route({
        jsonrpc: '2.0',
        id: 'no-server-1',
        method: 'tools/call',
        params: { name: 'nonexistent_server/some_tool', arguments: {} },
      });

      expect(response.error).toBeDefined();
      expect(response.error?.code).toBe(-32601);
    });

    it('returns method not found for unsupported methods', async () => {
      router = new McpRouter();

      const response = await router.route({
        jsonrpc: '2.0',
        id: 'bad-method',
        method: 'completions/complete',
      });

      expect(response.error).toBeDefined();
      expect(response.error?.code).toBe(-32601);
      expect(response.error?.message).toContain('Method not found');
    });
  });

  // -----------------------------------------------------------------------
  // 6. Health monitoring
  // -----------------------------------------------------------------------
  describe('Health monitoring', () => {
    it('reports connected when mcpd is healthy', async () => {
      mockMcpd = await startMockMcpd({
        instances: [
          { name: 'slack', status: 'running' },
          { name: 'github', status: 'running' },
        ],
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      const registry = new ProviderRegistry();
      registry.register(createMockLlmProvider('test-health', () => '{}'));

      const monitor = new TieredHealthMonitor({
        mcpdClient: client,
        providerRegistry: registry,
        mcpdUrl: mockMcpd.baseUrl,
      });

      // Need to set router just so afterEach cleanup works
      router = new McpRouter();

      const health = await monitor.checkHealth();

      expect(health.mcplocal.status).toBe('healthy');
      expect(health.mcplocal.llmProvider).toBe('test-health');
      expect(health.mcpd.status).toBe('connected');
      expect(health.mcpd.url).toBe(mockMcpd.baseUrl);
      expect(health.instances).toHaveLength(2);
      expect(health.instances[0]).toEqual({ name: 'slack', status: 'running' });
      expect(health.instances[1]).toEqual({ name: 'github', status: 'running' });
    });

    it('reports disconnected when mcpd is unreachable', async () => {
      // Point at a port nothing is listening on
      const client = new McpdClient('http://127.0.0.1:1', 'token');
      const registry = new ProviderRegistry();

      const monitor = new TieredHealthMonitor({
        mcpdClient: client,
        providerRegistry: registry,
        mcpdUrl: 'http://127.0.0.1:1',
      });

      router = new McpRouter();

      const health = await monitor.checkHealth();

      expect(health.mcplocal.status).toBe('healthy');
      expect(health.mcpd.status).toBe('disconnected');
      expect(health.instances).toEqual([]);
    });

    it('reports disconnected when mcpdClient is null', async () => {
      const registry = new ProviderRegistry();

      const monitor = new TieredHealthMonitor({
        mcpdClient: null,
        providerRegistry: registry,
        mcpdUrl: 'http://localhost:3100',
      });

      router = new McpRouter();

      const health = await monitor.checkHealth();

      expect(health.mcpd.status).toBe('disconnected');
      expect(health.instances).toEqual([]);
    });

    it('reports null LLM provider when none registered', async () => {
      const registry = new ProviderRegistry();

      const monitor = new TieredHealthMonitor({
        mcpdClient: null,
        providerRegistry: registry,
        mcpdUrl: 'http://localhost:3100',
      });

      router = new McpRouter();

      const health = await monitor.checkHealth();
      expect(health.mcplocal.llmProvider).toBeNull();
    });
  });

  // -----------------------------------------------------------------------
  // 7. Auth token propagation
  // -----------------------------------------------------------------------
  describe('Auth token propagation', () => {
    it('sends Bearer token in all requests to mcpd', async () => {
      const secretToken = 'super-secret-bearer-token-xyz';

      mockMcpd = await startMockMcpd({
        expectedToken: secretToken,
        servers: [{ id: 'srv-auth', name: 'authserver', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-auth:tools/list', {
            result: { tools: [{ name: 'protected_op', description: 'Protected operation' }] },
          }],
          ['srv-auth:tools/call', {
            result: { message: 'authorized access granted' },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, secretToken);
      router = new McpRouter();

      // Discovery calls
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // Tool call
      await router.route({
        jsonrpc: '2.0',
        id: 'auth-1',
        method: 'tools/call',
        params: { name: 'authserver/protected_op', arguments: {} },
      });

      // Verify all requests carried the correct Bearer token
      const requestLog = mockMcpd.config.requestLog;
      expect(requestLog.length).toBeGreaterThan(0);

      for (const entry of requestLog) {
        expect(entry.authHeader).toBe(`Bearer ${secretToken}`);
      }
    });

    it('receives 401 when token is wrong', async () => {
      mockMcpd = await startMockMcpd({
        expectedToken: 'correct-token',
        servers: [{ id: 'srv-sec', name: 'secserver', transport: 'stdio' }],
      });

      // Client uses the wrong token
      const client = new McpdClient(mockMcpd.baseUrl, 'wrong-token');
      router = new McpRouter();

      // refreshUpstreams should fail because mcpd rejects the auth
      await expect(refreshUpstreams(router, client)).rejects.toThrow();
    });

    it('propagates token through McpdUpstream to proxy requests', async () => {
      const token = 'upstream-bearer-token';

      mockMcpd = await startMockMcpd({
        expectedToken: token,
        servers: [],
        proxyResponses: new Map([
          ['srv-direct:tools/call', {
            result: { data: 'success' },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, token);
      router = new McpRouter();

      // Manually create and add an upstream (bypassing discovery)
      const upstream = new McpdUpstream('srv-direct', 'directserver', client);
      router.addUpstream(upstream);

      // Send a direct request through the upstream
      const request: JsonRpcRequest = {
        jsonrpc: '2.0',
        id: 'direct-auth-1',
        method: 'tools/call',
        params: { name: 'test_tool', arguments: {} },
      };
      const response = await upstream.send(request);

      expect(response.result).toEqual({ data: 'success' });

      // Verify the proxy request carried the Bearer token
      const proxyRequests = mockMcpd.config.requestLog.filter(
        (r) => r.url === '/api/v1/mcp/proxy',
      );
      expect(proxyRequests.length).toBeGreaterThan(0);
      for (const req of proxyRequests) {
        expect(req.authHeader).toBe(`Bearer ${token}`);
      }
    });
  });

  // -----------------------------------------------------------------------
  // Combined multi-tier scenario
  // -----------------------------------------------------------------------
  describe('Full multi-tier scenario', () => {
    it('exercises the complete lifecycle: init -> discover -> call -> filter -> health', async () => {
      const largeResult = {
        records: Array.from({ length: 50 }, (_, i) => ({
          id: i,
          title: `Record ${String(i)}`,
          body: 'x'.repeat(40),
        })),
      };
      const filteredResult = {
        records: [{ id: 0, title: 'Record 0' }, { id: 1, title: 'Record 1' }],
      };

      mockMcpd = await startMockMcpd({
        servers: [
          { id: 'srv-wiki', name: 'wiki', transport: 'stdio' },
          { id: 'srv-jira', name: 'jira', transport: 'stdio' },
        ],
        proxyResponses: new Map([
          ['srv-wiki:tools/list', {
            result: {
              tools: [
                { name: 'search_pages', description: 'Search wiki pages' },
                { name: 'create_page', description: 'Create a wiki page' },
              ],
            },
          }],
          ['srv-jira:tools/list', {
            result: {
              tools: [
                { name: 'search_issues', description: 'Search Jira issues' },
                { name: 'create_issue', description: 'Create a Jira issue' },
              ],
            },
          }],
          ['srv-wiki:tools/call', {
            result: largeResult,
          }],
          ['srv-jira:tools/call', {
            result: { created: true, key: 'PROJ-123' },
          }],
        ]),
        instances: [
          { name: 'wiki', status: 'running' },
          { name: 'jira', status: 'running' },
        ],
      });

      const token = mockMcpd.config.expectedToken;
      const client = new McpdClient(mockMcpd.baseUrl, token);
      router = new McpRouter();

      // Step 1: Initialize proxy
      const initResponse = await router.route({
        jsonrpc: '2.0',
        id: 'lifecycle-init',
        method: 'initialize',
      });
      expect(initResponse.error).toBeUndefined();

      // Step 2: Discover servers
      const registered = await refreshUpstreams(router, client);
      expect(registered.sort()).toEqual(['jira', 'wiki']);

      // Step 3: Discover tools
      const tools = await router.discoverTools();
      expect(tools).toHaveLength(4);

      // Step 4: Set up LLM filtering
      const registry = new ProviderRegistry();
      const mockProvider = createMockLlmProvider('lifecycle-llm', () => {
        return JSON.stringify(filteredResult);
      });
      registry.register(mockProvider);

      const processor = new LlmProcessor(registry, {
        ...DEFAULT_PROCESSOR_CONFIG,
        enableFiltering: true,
        tokenThreshold: 10,
      });
      router.setLlmProcessor(processor);

      // Step 5: Call a search tool -> should get filtered response
      const searchResponse = await router.route({
        jsonrpc: '2.0',
        id: 'lifecycle-search',
        method: 'tools/call',
        params: { name: 'wiki/search_pages', arguments: { query: 'deployment' } },
      });
      expect(searchResponse.error).toBeUndefined();
      const searchResult = searchResponse.result as { records: Array<{ id: number; title: string }> };
      expect(searchResult.records).toHaveLength(2); // Filtered down

      // Step 6: Call a create tool -> should bypass LLM
      const createResponse = await router.route({
        jsonrpc: '2.0',
        id: 'lifecycle-create',
        method: 'tools/call',
        params: { name: 'jira/create_issue', arguments: { summary: 'New bug' } },
      });
      expect(createResponse.error).toBeUndefined();
      expect((createResponse.result as Record<string, unknown>)['key']).toBe('PROJ-123');

      // Step 7: Check health
      const monitor = new TieredHealthMonitor({
        mcpdClient: client,
        providerRegistry: registry,
        mcpdUrl: mockMcpd.baseUrl,
      });
      const health = await monitor.checkHealth();
      expect(health.mcplocal.status).toBe('healthy');
      expect(health.mcplocal.llmProvider).toBe('lifecycle-llm');
      expect(health.mcpd.status).toBe('connected');
      expect(health.instances).toHaveLength(2);

      // Step 8: Verify all requests used auth
      for (const entry of mockMcpd.config.requestLog) {
        expect(entry.authHeader).toBe(`Bearer ${token}`);
      }
    });
  });

  // -----------------------------------------------------------------------
  // 8. Smart pagination through the full pipeline
  // -----------------------------------------------------------------------
  describe('Smart pagination', () => {
    // Helper: generate a large JSON response (~100KB)
    function makeLargeToolResult(): { flows: Array<{ id: string; type: string; label: string; wires: string[] }> } {
      return {
        flows: Array.from({ length: 200 }, (_, i) => ({
          id: `flow-${String(i).padStart(4, '0')}`,
          type: i % 3 === 0 ? 'function' : i % 3 === 1 ? 'http request' : 'inject',
          label: `Node ${String(i)}: ${i % 3 === 0 ? 'Data transform' : i % 3 === 1 ? 'API call' : 'Timer trigger'}`,
          wires: [`flow-${String(i + 1).padStart(4, '0')}`],
        })),
      };
    }

    it('paginates large tool response with smart AI summaries through router', async () => {
      const largeResult = makeLargeToolResult();

      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-nodered', name: 'node-red', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-nodered:tools/list', {
            result: { tools: [{ name: 'get_flows', description: 'Get all flows' }] },
          }],
          ['srv-nodered:tools/call', {
            result: largeResult,
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // Set up paginator with LLM provider for smart summaries
      const registry = new ProviderRegistry();
      const completeFn = vi.fn().mockImplementation(() => ({
        content: JSON.stringify([
          { page: 1, summary: 'Function nodes and data transforms (flow-0000 through flow-0050)' },
          { page: 2, summary: 'HTTP request nodes and API integrations (flow-0051 through flow-0100)' },
          { page: 3, summary: 'Inject/timer nodes and triggers (flow-0101 through flow-0150)' },
          { page: 4, summary: 'Remaining nodes and wire connections (flow-0151 through flow-0199)' },
        ]),
      }));
      const mockProvider: LlmProvider = {
        name: 'test-paginator',
        isAvailable: () => true,
        complete: completeFn,
      };
      registry.register(mockProvider);

      // Low threshold so our response triggers pagination
      const paginator = new ResponsePaginator(registry, {
        sizeThreshold: 1000,
        pageSize: 8000,
      });
      router.setPaginator(paginator);

      // Call the tool — should get pagination index, not raw data
      const response = await router.route({
        jsonrpc: '2.0',
        id: 'paginate-1',
        method: 'tools/call',
        params: { name: 'node-red/get_flows', arguments: {} },
      });

      expect(response.error).toBeUndefined();
      const result = response.result as { content: Array<{ type: string; text: string }> };
      expect(result.content).toHaveLength(1);
      const indexText = result.content[0]!.text;

      // Verify smart index with AI summaries
      expect(indexText).toContain('AI-generated summaries');
      expect(indexText).toContain('Function nodes and data transforms');
      expect(indexText).toContain('HTTP request nodes');
      expect(indexText).toContain('_resultId');
      expect(indexText).toContain('_page');

      // LLM was called to generate summaries
      expect(completeFn).toHaveBeenCalledOnce();
      const llmCall = completeFn.mock.calls[0]![0]!;
      expect(llmCall.messages[0].role).toBe('system');
      expect(llmCall.messages[1].content).toContain('node-red/get_flows');
    });

    it('retrieves specific pages after pagination via _resultId/_page', async () => {
      const largeResult = makeLargeToolResult();

      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-nodered', name: 'node-red', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-nodered:tools/list', {
            result: { tools: [{ name: 'get_flows', description: 'Get all flows' }] },
          }],
          ['srv-nodered:tools/call', {
            result: largeResult,
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // Simple paginator (no LLM) for predictable behavior
      const paginator = new ResponsePaginator(null, {
        sizeThreshold: 1000,
        pageSize: 8000,
      });
      router.setPaginator(paginator);

      // First call — get the pagination index
      const indexResponse = await router.route({
        jsonrpc: '2.0',
        id: 'idx-1',
        method: 'tools/call',
        params: { name: 'node-red/get_flows', arguments: {} },
      });

      expect(indexResponse.error).toBeUndefined();
      const indexResult = indexResponse.result as { content: Array<{ text: string }> };
      const indexText = indexResult.content[0]!.text;
      const resultIdMatch = /"_resultId": "([^"]+)"/.exec(indexText);
      expect(resultIdMatch).not.toBeNull();
      const resultId = resultIdMatch![1]!;

      // Second call — retrieve page 1 via _resultId/_page
      const page1Response = await router.route({
        jsonrpc: '2.0',
        id: 'page-1',
        method: 'tools/call',
        params: {
          name: 'node-red/get_flows',
          arguments: { _resultId: resultId, _page: 1 },
        },
      });

      expect(page1Response.error).toBeUndefined();
      const page1Result = page1Response.result as { content: Array<{ text: string }> };
      expect(page1Result.content[0]!.text).toContain('Page 1/');
      // Page content should contain flow data
      expect(page1Result.content[0]!.text).toContain('flow-');

      // Third call — retrieve page 2
      const page2Response = await router.route({
        jsonrpc: '2.0',
        id: 'page-2',
        method: 'tools/call',
        params: {
          name: 'node-red/get_flows',
          arguments: { _resultId: resultId, _page: 2 },
        },
      });

      expect(page2Response.error).toBeUndefined();
      const page2Result = page2Response.result as { content: Array<{ text: string }> };
      expect(page2Result.content[0]!.text).toContain('Page 2/');
    });

    it('retrieves full content with _page=all', async () => {
      const largeResult = makeLargeToolResult();

      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-nodered', name: 'node-red', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-nodered:tools/list', {
            result: { tools: [{ name: 'get_flows', description: 'Get all flows' }] },
          }],
          ['srv-nodered:tools/call', {
            result: largeResult,
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      const paginator = new ResponsePaginator(null, {
        sizeThreshold: 1000,
        pageSize: 8000,
      });
      router.setPaginator(paginator);

      // Get index
      const indexResponse = await router.route({
        jsonrpc: '2.0',
        id: 'all-idx',
        method: 'tools/call',
        params: { name: 'node-red/get_flows', arguments: {} },
      });
      const indexText = (indexResponse.result as { content: Array<{ text: string }> }).content[0]!.text;
      const resultId = /"_resultId": "([^"]+)"/.exec(indexText)![1]!;

      // Request all pages
      const allResponse = await router.route({
        jsonrpc: '2.0',
        id: 'all-1',
        method: 'tools/call',
        params: {
          name: 'node-red/get_flows',
          arguments: { _resultId: resultId, _page: 'all' },
        },
      });

      expect(allResponse.error).toBeUndefined();
      const allResult = allResponse.result as { content: Array<{ text: string }> };
      // Full response should contain the original JSON
      const fullText = allResult.content[0]!.text;
      expect(fullText).toContain('flow-0000');
      expect(fullText).toContain('flow-0199');
      // Should be the full serialized result
      expect(JSON.parse(fullText)).toEqual(largeResult);
    });

    it('falls back to simple index when LLM fails', async () => {
      const largeResult = makeLargeToolResult();

      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-nodered', name: 'node-red', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-nodered:tools/list', {
            result: { tools: [{ name: 'get_flows', description: 'Get all flows' }] },
          }],
          ['srv-nodered:tools/call', {
            result: largeResult,
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // Set up paginator with a failing LLM
      const registry = new ProviderRegistry();
      registry.register(createFailingLlmProvider('broken-llm'));
      const paginator = new ResponsePaginator(registry, {
        sizeThreshold: 1000,
        pageSize: 8000,
      });
      router.setPaginator(paginator);

      const response = await router.route({
        jsonrpc: '2.0',
        id: 'fallback-idx',
        method: 'tools/call',
        params: { name: 'node-red/get_flows', arguments: {} },
      });

      expect(response.error).toBeUndefined();
      const text = (response.result as { content: Array<{ text: string }> }).content[0]!.text;
      // Should still paginate, just without AI summaries
      expect(text).toContain('_resultId');
      expect(text).not.toContain('AI-generated summaries');
      expect(text).toContain('Page 1:');
    });

    it('returns expired cache message for stale _resultId', async () => {
      router = new McpRouter();
      const paginator = new ResponsePaginator(null, { sizeThreshold: 100, pageSize: 50 });
      router.setPaginator(paginator);

      // Try to retrieve a page with an unknown resultId
      const response = await router.route({
        jsonrpc: '2.0',
        id: 'stale-1',
        method: 'tools/call',
        params: {
          name: 'anything/tool',
          arguments: { _resultId: 'nonexistent-id', _page: 1 },
        },
      });

      expect(response.error).toBeUndefined();
      const text = (response.result as { content: Array<{ text: string }> }).content[0]!.text;
      expect(text).toContain('expired');
      expect(text).toContain('re-call');
    });

    it('skips pagination for small responses', async () => {
      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-small', name: 'smallserver', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-small:tools/list', {
            result: { tools: [{ name: 'get_status', description: 'Get status' }] },
          }],
          ['srv-small:tools/call', {
            result: { status: 'ok', uptime: 12345 },
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      const paginator = new ResponsePaginator(null, { sizeThreshold: 80000, pageSize: 40000 });
      router.setPaginator(paginator);

      const response = await router.route({
        jsonrpc: '2.0',
        id: 'small-1',
        method: 'tools/call',
        params: { name: 'smallserver/get_status', arguments: {} },
      });

      expect(response.error).toBeUndefined();
      // Should return the raw result directly, not a pagination index
      expect(response.result).toEqual({ status: 'ok', uptime: 12345 });
    });

    it('handles markdown-fenced LLM responses (Gemini quirk)', async () => {
      const largeResult = makeLargeToolResult();

      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-nodered', name: 'node-red', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-nodered:tools/list', {
            result: { tools: [{ name: 'get_flows', description: 'Get all flows' }] },
          }],
          ['srv-nodered:tools/call', {
            result: largeResult,
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      // Simulate Gemini wrapping JSON in ```json fences
      const registry = new ProviderRegistry();
      const mockProvider: LlmProvider = {
        name: 'gemini-mock',
        isAvailable: () => true,
        complete: vi.fn().mockResolvedValue({
          content: '```json\n' + JSON.stringify([
            { page: 1, summary: 'Climate automation flows' },
            { page: 2, summary: 'Lighting control flows' },
          ]) + '\n```',
        }),
      };
      registry.register(mockProvider);

      const paginator = new ResponsePaginator(registry, {
        sizeThreshold: 1000,
        pageSize: 8000,
      });
      router.setPaginator(paginator);

      const response = await router.route({
        jsonrpc: '2.0',
        id: 'fence-1',
        method: 'tools/call',
        params: { name: 'node-red/get_flows', arguments: {} },
      });

      expect(response.error).toBeUndefined();
      const text = (response.result as { content: Array<{ text: string }> }).content[0]!.text;
      // Fences were stripped — smart summaries should appear
      expect(text).toContain('AI-generated summaries');
      expect(text).toContain('Climate automation flows');
      expect(text).toContain('Lighting control flows');
    });

    it('passes model override to LLM when project has custom model', async () => {
      const largeResult = makeLargeToolResult();

      mockMcpd = await startMockMcpd({
        servers: [{ id: 'srv-nodered', name: 'node-red', transport: 'stdio' }],
        proxyResponses: new Map([
          ['srv-nodered:tools/list', {
            result: { tools: [{ name: 'get_flows', description: 'Get all flows' }] },
          }],
          ['srv-nodered:tools/call', {
            result: largeResult,
          }],
        ]),
      });

      const client = new McpdClient(mockMcpd.baseUrl, mockMcpd.config.expectedToken);
      router = new McpRouter();
      await refreshUpstreams(router, client);
      await router.discoverTools();

      const registry = new ProviderRegistry();
      const completeFn = vi.fn().mockResolvedValue({
        content: JSON.stringify([{ page: 1, summary: 'test' }]),
      });
      const mockProvider: LlmProvider = {
        name: 'test-model-override',
        isAvailable: () => true,
        complete: completeFn,
      };
      registry.register(mockProvider);

      // Paginator with per-project model override
      const paginator = new ResponsePaginator(registry, {
        sizeThreshold: 1000,
        pageSize: 80000, // One big page so we get exactly 1 summary
      }, 'gemini-2.5-pro');
      router.setPaginator(paginator);

      await router.route({
        jsonrpc: '2.0',
        id: 'model-1',
        method: 'tools/call',
        params: { name: 'node-red/get_flows', arguments: {} },
      });

      // Verify the model was passed through to the LLM call
      expect(completeFn).toHaveBeenCalledOnce();
      const llmOpts = completeFn.mock.calls[0]![0]!;
      expect(llmOpts.model).toBe('gemini-2.5-pro');
    });
  });
});
