/// <reference types="jest" />
/**
 * Tests for MCP Server
 */

import { createMockSessionManager } from './utils/mock-session';

// Mock CDP client for reconnection tests
const mockForceReconnect = jest.fn().mockResolvedValue(undefined);
jest.mock('../src/cdp/client', () => ({
  getCDPClient: jest.fn(() => ({
    forceReconnect: mockForceReconnect,
  })),
}));

// Mock the session manager
jest.mock('../src/session-manager', () => ({
  getSessionManager: jest.fn(),
}));

import { getSessionManager } from '../src/session-manager';
import { MCPServer } from '../src/mcp-server';
import { MCPRequest, MCPErrorCodes, MCPToolDefinition } from '../src/types/mcp';
import { DEFAULT_TOOL_EXECUTION_TIMEOUT_MS, DEFAULT_RECONNECT_TIMEOUT_MS } from '../src/config/defaults';

// Helper type for response with result
interface MCPResultResponse {
  jsonrpc: string;
  id: number | string;
  result: {
    protocolVersion?: string;
    capabilities?: unknown;
    serverInfo?: { name: string; version: string };
    instructions?: string;
    tools?: Array<{ name: string }>;
    content?: Array<{ type: string; text: string }>;
    isError?: boolean;
  };
  error?: {
    code: number;
    message: string;
  };
}

describe('MCPServer', () => {
  let mockSessionManager: ReturnType<typeof createMockSessionManager>;
  let server: MCPServer;

  beforeEach(() => {
    mockSessionManager = createMockSessionManager();
    (getSessionManager as jest.Mock).mockReturnValue(mockSessionManager);
    server = new MCPServer(mockSessionManager as any);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('Protocol Handling', () => {
    describe('initialize', () => {
      test('returns protocol version and capabilities', async () => {
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'initialize',
          params: {},
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        expect(response.jsonrpc).toBe('2.0');
        expect(response.id).toBe(1);
        expect(response.result).toBeDefined();
        expect(response.result!.protocolVersion).toBeDefined();
        expect(response.result!.capabilities).toBeDefined();
        expect(response.result!.serverInfo).toBeDefined();
        expect(response.result!.serverInfo!.name).toBe('openchrome');
      });

      test('returns server version', async () => {
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'initialize',
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        expect(response.result!.serverInfo!.version).toBeDefined();
      });

      test('does NOT include instructions field (MCP protocol conformance)', async () => {
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'initialize',
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        // MCP 2024-11-05 spec: InitializeResult only allows protocolVersion, capabilities, serverInfo
        expect(response.result!.instructions).toBeUndefined();
      });

      test('result contains only spec-allowed keys', async () => {
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'initialize',
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;
        const keys = Object.keys(response.result!).sort();

        expect(keys).toEqual(['capabilities', 'protocolVersion', 'serverInfo']);
      });
    });

    describe('initialized', () => {
      test('handles initialized as a known method without error', async () => {
        // initialized is handled as a notification in the stdio line handler,
        // but when called via handleRequest directly it should not error
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 2,
          method: 'notifications/initialized',
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        // Should return a valid response (not an error)
        expect(response.jsonrpc).toBe('2.0');
      });
    });

    describe('unknown method', () => {
      test('returns method not found error', async () => {
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'unknown/method',
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        expect(response.error).toBeDefined();
        expect(response.error!.code).toBe(MCPErrorCodes.METHOD_NOT_FOUND);
        expect(response.error!.message).toContain('Unknown method');
      });
    });
  });

  describe('Tool Registration', () => {
    test('registers tools correctly', () => {
      const handler = jest.fn();
      const definition: MCPToolDefinition = {
        name: 'test_tool',
        description: 'A test tool',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };

      server.registerTool('test_tool', handler, definition);

      expect(server.getToolNames()).toContain('test_tool');
    });

    test('returns registered tools via tools/list when full exposure is requested', async () => {
      server = new MCPServer(mockSessionManager as any, { initialToolTier: 3 });
      const handler = jest.fn();
      const definition: MCPToolDefinition = {
        name: 'my_tool',
        description: 'My tool',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('my_tool', handler, definition);

      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/list',
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(response.result!.tools).toBeDefined();
      expect(response.result!.tools!.length).toBeGreaterThan(0);
      expect(response.result!.tools!.some((t) => t.name === 'my_tool')).toBe(true);
    });
  });

  describe('Tool Execution', () => {
    test('calls tool handler with correct arguments', async () => {
      const handler = jest.fn().mockResolvedValue({
        content: [{ type: 'text', text: 'Success' }],
      });

      const definition: MCPToolDefinition = {
        name: 'my_tool',
        description: 'Test tool',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('my_tool', handler, definition);

      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: {
          name: 'my_tool',
          arguments: { foo: 'bar' },
          sessionId: 'test-session',
        },
      };

      await server.handleRequest(request);

      expect(handler).toHaveBeenCalledWith('test-session', { foo: 'bar' }, expect.objectContaining({ startTime: expect.any(Number), deadlineMs: expect.any(Number) }));
    });

    test('returns tool result', async () => {
      const handler = jest.fn().mockResolvedValue({
        content: [{ type: 'text', text: 'Tool executed successfully' }],
      });

      const definition: MCPToolDefinition = {
        name: 'my_tool',
        description: 'Test tool',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('my_tool', handler, definition);

      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: {
          name: 'my_tool',
          arguments: {},
        },
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(response.result!.content).toBeDefined();
      expect(response.result!.content![0].text).toBe('Tool executed successfully');
    });

    test('returns error for unknown tool', async () => {
      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: {
          name: 'nonexistent_tool',
          arguments: {},
        },
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(response.error).toBeDefined();
      expect(response.error!.message).toContain('Unknown tool');
    });

    test('returns error for missing tool name', async () => {
      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: {
          arguments: {},
        },
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(response.error).toBeDefined();
      expect(response.error!.message).toContain('Missing tool name');
    });

    test('handles tool execution errors gracefully', async () => {
      const handler = jest.fn().mockRejectedValue(new Error('Tool failed'));

      const definition: MCPToolDefinition = {
        name: 'failing_tool',
        description: 'A failing tool',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('failing_tool', handler, definition);

      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: {
          name: 'failing_tool',
          arguments: {},
        },
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(response.result!.isError).toBe(true);
      expect(response.result!.content![0].text).toContain('Tool failed');
    });

    test('ensures session exists before tool execution', async () => {
      const handler = jest.fn().mockResolvedValue({
        content: [{ type: 'text', text: 'OK' }],
      });

      const definition: MCPToolDefinition = {
        name: 'my_tool',
        description: 'Test',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('my_tool', handler, definition);

      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: {
          name: 'my_tool',
          arguments: {},
          sessionId: 'new-session-123',
        },
      };

      await server.handleRequest(request);

      expect(mockSessionManager.getOrCreateSession).toHaveBeenCalledWith(
        'new-session-123',
        expect.objectContaining({ label: 'session-init' }),
      );
    });

    test('skips session initialization for orphan reaping recovery tool', async () => {
      const handler = jest.fn().mockResolvedValue({
        content: [{ type: 'text', text: '{"killed":0}' }],
      });

      const definition: MCPToolDefinition = {
        name: 'oc_reap_orphans',
        description: 'Test recovery tool',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('oc_reap_orphans', handler, definition);

      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: {
          name: 'oc_reap_orphans',
          arguments: {},
          sessionId: 'broken-session-123',
        },
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(response.result!.content![0].text).toBe('{"killed":0}');
      expect(mockSessionManager.getOrCreateSession).not.toHaveBeenCalled();
      expect(handler).toHaveBeenCalledWith('broken-session-123', {}, expect.objectContaining({ startTime: expect.any(Number) }));
    });
  });

  describe('Session Management APIs', () => {
    describe('sessions/list', () => {
      test('returns list of sessions', async () => {
        await mockSessionManager.createSession({ id: 'session-1', name: 'First' });
        await mockSessionManager.createSession({ id: 'session-2', name: 'Second' });

        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'sessions/list',
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        expect(response.result!.content).toBeDefined();
        const sessions = JSON.parse(response.result!.content![0].text);
        expect(sessions.length).toBe(2);
      });

      test('returns empty list when no sessions', async () => {
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'sessions/list',
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        const sessions = JSON.parse(response.result!.content![0].text);
        expect(sessions).toEqual([]);
      });
    });

    describe('sessions/create', () => {
      test('creates new session with generated ID', async () => {
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'sessions/create',
          params: {},
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        const result = JSON.parse(response.result!.content![0].text);
        expect(result.sessionId).toBeDefined();
      });

      test('creates session with specified ID', async () => {
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'sessions/create',
          params: {
            sessionId: 'my-custom-session',
          },
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        const result = JSON.parse(response.result!.content![0].text);
        expect(result.sessionId).toBe('my-custom-session');
      });

      test('creates session with custom name', async () => {
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'sessions/create',
          params: {
            name: 'My Named Session',
          },
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        const result = JSON.parse(response.result!.content![0].text);
        expect(result.name).toBe('My Named Session');
      });
    });

    describe('sessions/delete', () => {
      test('deletes existing session', async () => {
        await mockSessionManager.createSession({ id: 'to-delete' });

        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'sessions/delete',
          params: {
            sessionId: 'to-delete',
          },
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        expect(response.result!.content![0].text).toContain('deleted');
        expect(mockSessionManager.deleteSession).toHaveBeenCalledWith('to-delete');
      });

      test('returns error for missing sessionId', async () => {
        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'sessions/delete',
          params: {},
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        expect(response.error).toBeDefined();
        expect(response.error!.message).toContain('Missing sessionId');
      });
    });
  });

  describe('Error Response Format', () => {
    test('includes correct jsonrpc version in error response', async () => {
      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 123,
        method: 'invalid/method',
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(response.jsonrpc).toBe('2.0');
      expect(response.id).toBe(123);
    });

    test('preserves request ID in error response', async () => {
      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 'string-id-456',
        method: 'invalid/method',
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(response.id).toBe('string-id-456');
    });
  });

  describe('getSessionManager', () => {
    test('returns the session manager instance', () => {
      const sm = server.getSessionManager();
      expect(sm).toBeDefined();
    });
  });

  describe('getToolNames', () => {
    test('returns empty array when no tools registered', () => {
      const newServer = new MCPServer(mockSessionManager as any);
      expect(newServer.getToolNames()).toEqual([]);
    });

    test('returns all registered tool names', () => {
      const definition1: MCPToolDefinition = {
        name: 'tool1',
        description: '',
        inputSchema: { type: 'object' as const, properties: {} },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      const definition2: MCPToolDefinition = {
        name: 'tool2',
        description: '',
        inputSchema: { type: 'object' as const, properties: {} },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('tool1', jest.fn(), definition1);
      server.registerTool('tool2', jest.fn(), definition2);

      const names = server.getToolNames();

      expect(names).toContain('tool1');
      expect(names).toContain('tool2');
      expect(names.length).toBe(2);
    });
  });

  describe('Connection Error Recovery', () => {
    beforeEach(() => {
      mockForceReconnect.mockClear();
    });

    test('retries tool call after connection error and reconnect', async () => {
      let callCount = 0;
      const handler = jest.fn().mockImplementation(() => {
        callCount++;
        if (callCount === 1) {
          throw new Error('Not connected to Chrome. Call connect() first.');
        }
        return { content: [{ type: 'text', text: 'Success after retry' }] };
      });

      const definition: MCPToolDefinition = {
        name: 'retry_tool',
        description: 'Test retry',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('retry_tool', handler, definition);

      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: { name: 'retry_tool', arguments: {} },
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(handler).toHaveBeenCalledTimes(2);
      expect(mockForceReconnect).toHaveBeenCalledTimes(1);
      expect(response.result!.content![0].text).toBe('Success after retry');
      expect(response.result!.isError).toBeUndefined();
    });

    test('returns error with reconnection guidance when retry also fails', async () => {
      const handler = jest.fn().mockRejectedValue(
        new Error('Connection closed')
      );

      const definition: MCPToolDefinition = {
        name: 'fail_tool',
        description: 'Always fails',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('fail_tool', handler, definition);

      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: { name: 'fail_tool', arguments: {} },
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(response.result!.isError).toBe(true);
      expect(response.result!.content![0].text).toContain('Connection closed');
      expect(response.result!.content![0].text).toContain('auto-reconnect was attempted');
      expect(response.result!.content![0].text).toContain('tabs_context');
      expect(mockForceReconnect).toHaveBeenCalledTimes(1);
    });

    test('does not retry for non-connection errors', async () => {
      const handler = jest.fn().mockRejectedValue(
        new Error('Element not found')
      );

      const definition: MCPToolDefinition = {
        name: 'normal_error_tool',
        description: 'Normal error',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('normal_error_tool', handler, definition);

      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: { name: 'normal_error_tool', arguments: {} },
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(handler).toHaveBeenCalledTimes(1);
      expect(mockForceReconnect).not.toHaveBeenCalled();
      expect(response.result!.isError).toBe(true);
      expect(response.result!.content![0].text).toContain('Element not found');
      expect(response.result!.content![0].text).not.toContain('/mcp');
    });

    test('detects various connection error patterns', async () => {
      const connectionErrors = [
        'Not connected to Chrome. Call connect() first.',
        'Protocol error (Runtime.callFunctionOn): Session closed.',
        'WebSocket is not open: readyState 3 (CLOSED)',
        'Navigation failed because browser has disconnected',
        'Execution context was destroyed',
        'Target closed',
      ];

      for (const errorMsg of connectionErrors) {
        let callCount = 0;
        const handler = jest.fn().mockImplementation(() => {
          callCount++;
          if (callCount === 1) throw new Error(errorMsg);
          return { content: [{ type: 'text', text: 'OK' }] };
        });

        const toolName = `test_tool_${errorMsg.slice(0, 10).replace(/\W/g, '_')}`;
        const definition: MCPToolDefinition = {
          name: toolName,
          description: 'Test',
          inputSchema: { type: 'object' as const, properties: {}, required: [] },
          annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
        };
        server.registerTool(toolName, handler, definition);

        const request: MCPRequest = {
          jsonrpc: '2.0',
          id: 1,
          method: 'tools/call',
          params: { name: toolName, arguments: {} },
        };

        const response = (await server.handleRequest(request)) as MCPResultResponse;
        expect(handler).toHaveBeenCalledTimes(2);
        mockForceReconnect.mockClear();
      }
    });

    // L1 (issue #1469 / SSOT D4 & D5): the swallowed-connection-error retry path —
    // where a tool RETURNS an isError result instead of throwing — must apply the
    // same guards as the thrown-error path: a reconcile gate and a timeout race.
    describe('swallowed connection error retry (L1)', () => {
      const swallowedErr = 'WebSocket is not open: readyState 3 (CLOSED)';
      const makeTool = (name: string, handler: jest.Mock) => {
        const definition: MCPToolDefinition = {
          name,
          description: 'swallowed-error tool',
          inputSchema: { type: 'object' as const, properties: {}, required: [] },
          annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
        };
        server.registerTool(name, handler, definition);
        return { jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name, arguments: {} } } as MCPRequest;
      };

      test('retries and succeeds when handler returns (not throws) a connection error', async () => {
        let callCount = 0;
        const handler = jest.fn().mockImplementation(() => {
          callCount++;
          if (callCount === 1) {
            return { content: [{ type: 'text', text: swallowedErr }], isError: true };
          }
          return { content: [{ type: 'text', text: 'Recovered' }] };
        });
        const request = makeTool('swallowed_ok', handler);

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        expect(handler).toHaveBeenCalledTimes(2);
        expect(mockForceReconnect).toHaveBeenCalledTimes(1);
        expect(mockSessionManager.reconcileAfterReconnect).toHaveBeenCalledTimes(1);
        expect(response.result!.content![0].text).toBe('Recovered');
      });

      test('aborts the retry and keeps the original error when reconcile fails', async () => {
        (mockSessionManager.reconcileAfterReconnect as jest.Mock).mockRejectedValueOnce(
          new Error('reconcile failed'),
        );
        const handler = jest.fn().mockReturnValue({
          content: [{ type: 'text', text: swallowedErr }],
          isError: true,
        });
        const request = makeTool('swallowed_reconcile_fail', handler);

        const response = (await server.handleRequest(request)) as MCPResultResponse;

        // Reconcile threw → no second handler invocation, original error kept.
        expect(handler).toHaveBeenCalledTimes(1);
        expect(mockForceReconnect).toHaveBeenCalledTimes(1);
        expect(response.result!.isError).toBe(true);
        expect(response.result!.content![0].text).toContain('WebSocket is not open');
      });

      test('does not hang: the retry is bounded by the tool-execution timeout', async () => {
        jest.useFakeTimers();
        try {
          let callCount = 0;
          const handler = jest.fn().mockImplementation(() => {
            callCount++;
            if (callCount === 1) {
              return { content: [{ type: 'text', text: swallowedErr }], isError: true };
            }
            // Retry handler never resolves — only the timeout race can end the call.
            return new Promise(() => { /* never resolves */ });
          });
          const request = makeTool('swallowed_hang', handler);

          const responsePromise = server.handleRequest(request) as Promise<MCPResultResponse>;
          await jest.advanceTimersByTimeAsync(DEFAULT_TOOL_EXECUTION_TIMEOUT_MS + 50);
          const response = await responsePromise;

          expect(handler).toHaveBeenCalledTimes(2);
          // Retry timed out → catch keeps the original swallowed error result.
          expect(response.result!.isError).toBe(true);
          expect(response.result!.content![0].text).toContain('WebSocket is not open');
        } finally {
          jest.useRealTimers();
        }
      });

      test('does not hang when the reconnect itself stalls', async () => {
        jest.useFakeTimers();
        try {
          // forceReconnect never resolves — the reconnect timeout race must end the call.
          mockForceReconnect.mockReturnValueOnce(new Promise(() => { /* never resolves */ }));
          const handler = jest.fn().mockReturnValue({
            content: [{ type: 'text', text: swallowedErr }],
            isError: true,
          });
          const request = makeTool('swallowed_reconnect_hang', handler);

          const responsePromise = server.handleRequest(request) as Promise<MCPResultResponse>;
          await jest.advanceTimersByTimeAsync(DEFAULT_RECONNECT_TIMEOUT_MS + 50);
          const response = await responsePromise;

          // Reconnect timed out before the retry was dispatched → handler called once,
          // original error result preserved (never-hang honored at the reconnect step).
          expect(handler).toHaveBeenCalledTimes(1);
          expect(response.result!.isError).toBe(true);
          expect(response.result!.content![0].text).toContain('WebSocket is not open');
        } finally {
          jest.useRealTimers();
        }
      });
    });
  });

  describe('Reconnection Instructions', () => {
    test('connection error responses include retry reconnection guidance', async () => {
      const handler = jest.fn().mockRejectedValue(
        new Error('Connection closed')
      );

      const definition: MCPToolDefinition = {
        name: 'reconnect_test_tool',
        description: 'Test reconnection',
        inputSchema: { type: 'object' as const, properties: {}, required: [] },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
      };
      server.registerTool('reconnect_test_tool', handler, definition);

      const request: MCPRequest = {
        jsonrpc: '2.0',
        id: 1,
        method: 'tools/call',
        params: { name: 'reconnect_test_tool', arguments: {} },
      };

      const response = (await server.handleRequest(request)) as MCPResultResponse;

      expect(response.result!.isError).toBe(true);
      expect(response.result!.content![0].text).toContain('auto-reconnect was attempted');
      expect(response.result!.content![0].text).toContain('tabs_context');
    });
  });
});
