/**
 * E2E Tests for Remote MCP Server Orchestration
 *
 * Tests the gateway's ability to connect to and proxy requests to remote MCP servers:
 * - Public Mintlify docs MCP server (external)
 * - Local test MCP server (internal, on different port)
 *
 * Tests cover:
 * - Tool discovery from both remote servers
 * - Tool execution with proper proxying
 * - Resource discovery and reading
 * - Prompt discovery and execution
 * - Gateway caching behavior
 * - Error handling
 *
 * Set DEBUG_E2E=1 environment variable for verbose logging.
 */
import { test, expect, TestServer } from '@frontmcp/testing';

// Enable verbose logging only when DEBUG_E2E is set
const DEBUG = process.env['DEBUG_E2E'] === '1';
const log = DEBUG ? console.log.bind(console) : () => {};

// Local MCP server instance
let localMcpServer: TestServer | null = null;
// Mock Mintlify MCP server instance
let mockMintlifyServer: TestServer | null = null;

// Preferred port configuration from E2E_PORT_RANGES:
// - demo-e2e-remote: 50210-50219 (using 50211 for local MCP)
// - mock-api: 50910-50919 (using 50910 for mock Mintlify)
// Actual ports may differ if preferred ports are occupied (CI parallel runs).
const PREFERRED_MOCK_MINTLIFY_PORT = 50910;
const PREFERRED_LOCAL_MCP_PORT = 50211;

// Mutable env object — updated in beforeAll with actual allocated ports.
// test.use stores the reference, so mutations are visible when the gateway starts.
const gatewayEnv: Record<string, string> = {
  LOCAL_MCP_PORT: String(PREFERRED_LOCAL_MCP_PORT),
  MOCK_MINTLIFY_PORT: String(PREFERRED_MOCK_MINTLIFY_PORT),
};

// Start local MCP and mock Mintlify servers before all tests
beforeAll(async () => {
  log(`[E2E] Starting mock Mintlify server on preferred port ${PREFERRED_MOCK_MINTLIFY_PORT}...`);
  try {
    mockMintlifyServer = await TestServer.start({
      command: 'npx tsx apps/e2e/demo-e2e-remote/src/mock-mintlify-server/main.ts',
      project: 'mock-api',
      port: PREFERRED_MOCK_MINTLIFY_PORT,
      startupTimeout: 60000,
      healthCheckPath: '/',
      debug: DEBUG,
    });
    gatewayEnv.MOCK_MINTLIFY_PORT = String(mockMintlifyServer.info.port);
    log('[E2E] Mock Mintlify server started:', mockMintlifyServer.info.baseUrl);
  } catch (error) {
    console.error('[E2E] Failed to start mock Mintlify server:', error);
    throw error;
  }

  log(`[E2E] Starting local MCP server on preferred port ${PREFERRED_LOCAL_MCP_PORT}...`);
  try {
    localMcpServer = await TestServer.start({
      command: 'npx tsx apps/e2e/demo-e2e-remote/src/local-mcp-server/main.ts',
      project: 'demo-e2e-remote',
      port: PREFERRED_LOCAL_MCP_PORT,
      startupTimeout: 60000,
      healthCheckPath: '/', // Root path returns 404 which is OK for health check
      debug: DEBUG,
    });
    gatewayEnv.LOCAL_MCP_PORT = String(localMcpServer.info.port);
    log('[E2E] Local MCP server started:', localMcpServer.info.baseUrl);

    // Give the local server extra time to fully initialize
    // The health check passes when HTTP is ready, but MCP handlers need more time
    log('[E2E] Waiting for servers to fully initialize...');
    await new Promise((resolve) => setTimeout(resolve, 2000));
    log('[E2E] All servers should now be fully ready');
  } catch (error) {
    console.error('[E2E] Failed to start local MCP server:', error);
    throw error;
  }
}, 120000);

// Stop all servers after all tests
afterAll(async () => {
  if (localMcpServer) {
    log('[E2E] Stopping local MCP server...');
    await localMcpServer.stop();
    localMcpServer = null;
  }
  if (mockMintlifyServer) {
    log('[E2E] Stopping mock Mintlify server...');
    await mockMintlifyServer.stop();
    mockMintlifyServer = null;
  }
}, 30000);

test.describe('Remote MCP Server Orchestration E2E', () => {
  test.use({
    server: 'apps/e2e/demo-e2e-remote/src/main.ts',
    project: 'demo-e2e-remote',
    publicMode: true,
    logLevel: DEBUG ? 'debug' : 'warn',
    startupTimeout: 60000, // Give gateway more time to connect to servers
    env: gatewayEnv, // Reference to mutable object updated with actual ports in beforeAll
  });

  test('basic connectivity test', async ({ mcp }) => {
    log('[TEST] Running basic connectivity test');
    expect(mcp.isConnected()).toBe(true);
    log('[TEST] Server connected, name:', mcp.serverInfo.name);
  });

  test.describe('Connection & Discovery', () => {
    test('should connect to gateway server', async ({ mcp }) => {
      expect(mcp.isConnected()).toBe(true);
      expect(mcp.serverInfo.name).toBe('Remote Gateway E2E');
    });

    test('should discover tools from local MCP server', async ({ mcp }) => {
      const tools = await mcp.tools.list();
      log(
        '[TEST] Found tools:',
        tools.map((t: unknown) => {
          if (typeof t === 'object' && t !== null && 'name' in t) {
            return (t as { name: string }).name;
          }
          return String(t);
        }),
      );

      // Local tools should be namespaced with 'local:'
      expect(tools).toContainTool('local:echo');
      expect(tools).toContainTool('local:ping');
      expect(tools).toContainTool('local:add');
      expect(tools).toContainTool('local:slow-operation');
    });

    test('should discover tools from Mintlify MCP server', async ({ mcp }) => {
      const tools = await mcp.tools.list();

      // Mintlify tools should be namespaced with 'mintlify:'
      expect(tools).toContainTool('mintlify:SearchMintlify');
    });

    test('should discover resources from local MCP server', async ({ mcp }) => {
      const resources = await mcp.resources.list();

      expect(resources).toContainResource('test://status');
    });

    test('should discover prompts from local MCP server', async ({ mcp }) => {
      const prompts = await mcp.prompts.list();

      expect(prompts).toContainPrompt('local:greeting');
    });
  });

  test.describe('Local Tool Execution', () => {
    test('should call local echo tool', async ({ mcp }) => {
      const result = await mcp.tools.call('local:echo', { message: 'Hello Remote!' });

      expect(result).toBeSuccessful();
      expect(result).toHaveTextContent('Hello Remote!');
    });

    test('should call local ping tool', async ({ mcp }) => {
      const result = await mcp.tools.call('local:ping', {});

      expect(result).toBeSuccessful();
      expect(result).toHaveTextContent('pong');
      expect(result).toHaveTextContent('timestamp');
    });

    test('should call local add tool with input validation', async ({ mcp }) => {
      const result = await mcp.tools.call('local:add', { a: 5, b: 3 });

      expect(result).toBeSuccessful();
      expect(result).toHaveTextContent('8');
    });

    test('should call add tool with different numbers', async ({ mcp }) => {
      const result = await mcp.tools.call('local:add', { a: 100, b: -50 });

      expect(result).toBeSuccessful();
      expect(result).toHaveTextContent('50');
    });

    test('should handle slow operation tool', async ({ mcp }) => {
      const startTime = Date.now();
      const result = await mcp.tools.call('local:slow-operation', { delayMs: 500 });
      const elapsed = Date.now() - startTime;

      expect(result).toBeSuccessful();
      expect(result).toHaveTextContent('completed');
      expect(elapsed).toBeGreaterThanOrEqual(450); // Allow some tolerance
    });
  });

  test.describe('Mintlify Tool Execution', () => {
    test('should call Mintlify search tool', async ({ mcp }) => {
      const result = await mcp.tools.call('mintlify:SearchMintlify', {
        query: 'getting started',
      });

      expect(result).toBeSuccessful();
    });

    test('should search for specific topic', async ({ mcp }) => {
      const result = await mcp.tools.call('mintlify:SearchMintlify', {
        query: 'api documentation',
      });

      expect(result).toBeSuccessful();
    });
  });

  test.describe('Resource Access', () => {
    test('should read local status resource', async ({ mcp }) => {
      const result = await mcp.resources.read('test://status');

      expect(result).toBeSuccessful();
      expect(result).toHaveTextContent('healthy');
      expect(result).toHaveTextContent('local-test-mcp');
      expect(result).toHaveTextContent('0.1.0');
    });
  });

  test.describe('Prompt Access', () => {
    test('should get local greeting prompt', async ({ mcp }) => {
      const result = await mcp.prompts.get('local:greeting', { name: 'World' });

      expect(result).toBeSuccessful();
      expect(result.messages).toHaveLength(1);
    });

    test('should get greeting prompt with formal style', async ({ mcp }) => {
      const result = await mcp.prompts.get('local:greeting', {
        name: 'Developer',
        style: 'formal',
      });

      expect(result).toBeSuccessful();
      expect(result.messages.length).toBeGreaterThan(0);

      // Check if the message contains formal greeting
      const message = result.messages[0];
      if (message.content && typeof message.content === 'object' && 'text' in message.content) {
        expect(message.content.text).toContain('Developer');
      }
    });

    test('should get greeting prompt with casual style', async ({ mcp }) => {
      const result = await mcp.prompts.get('local:greeting', {
        name: 'Tester',
        style: 'casual',
      });

      expect(result).toBeSuccessful();
      expect(result.messages.length).toBeGreaterThan(0);
    });
  });

  test.describe('Gateway Caching', () => {
    test('should cache local ping responses', async ({ mcp }) => {
      // First call - should hit remote
      const result1 = await mcp.tools.call('local:ping', {});
      expect(result1).toBeSuccessful();
      expect(result1).toHaveTextContent('pong');
      expect(result1).toHaveTextContent('timestamp');

      // Second call - might return cached result
      const result2 = await mcp.tools.call('local:ping', {});
      expect(result2).toBeSuccessful();
      expect(result2).toHaveTextContent('pong');
      expect(result2).toHaveTextContent('timestamp');

      // Both calls should succeed (caching behavior is implementation-dependent)
      // Note: We don't compare timestamps since response structure is wrapped by testing framework
    });
  });

  test.describe('Error Handling', () => {
    test('should handle non-existent tool gracefully', async ({ mcp }) => {
      // MCP returns error objects with isError: true instead of throwing
      const result = await mcp.tools.call('local:non-existent-tool', {});
      expect(result.isError).toBe(true);
    });

    test('should handle missing required parameters', async ({ mcp }) => {
      // Echo tool requires 'message' parameter
      // MCP returns error objects with isError: true instead of throwing
      const result = await mcp.tools.call('local:echo', {});
      expect(result.isError).toBe(true);
    });

    test('should handle invalid parameter types', async ({ mcp }) => {
      // Add tool expects numbers, not strings
      // MCP returns error objects with isError: true instead of throwing
      const result = await mcp.tools.call('local:add', {
        a: 'not-a-number' as unknown as number,
        b: 5,
      });
      expect(result.isError).toBe(true);
    });
  });

  test.describe('Concurrent Operations', () => {
    test('should handle concurrent tool calls', async ({ mcp }) => {
      const promises = [
        mcp.tools.call('local:echo', { message: 'msg1' }),
        mcp.tools.call('local:echo', { message: 'msg2' }),
        mcp.tools.call('local:ping', {}),
        mcp.tools.call('local:add', { a: 1, b: 2 }),
      ];

      const results = await Promise.all(promises);

      for (const result of results) {
        expect(result).toBeSuccessful();
      }
    });

    test('should handle mixed local and remote calls', async ({ mcp }) => {
      const promises = [
        mcp.tools.call('local:ping', {}),
        mcp.tools.call('mintlify:SearchMintlify', { query: 'test' }),
        mcp.tools.call('local:echo', { message: 'concurrent' }),
      ];

      const results = await Promise.all(promises);

      for (const result of results) {
        expect(result).toBeSuccessful();
      }
    });
  });
});
