/**
 * @claude-flow/mcp - Test Suite
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
  createMCPServer,
  createToolRegistry,
  createSessionManager,
  createConnectionPool,
  createResourceRegistry,
  createPromptRegistry,
  createTaskManager,
  defineTool,
  definePrompt,
  textMessage,
  createTextResource,
  interpolate,
  ErrorCodes,
  MCPServerError,
  VERSION,
  MODULE_NAME,
} from '../src/index.js';
import type { ILogger, MCPTool } from '../src/types.js';

// Mock logger
const createMockLogger = (): ILogger => ({
  debug: vi.fn(),
  info: vi.fn(),
  warn: vi.fn(),
  error: vi.fn(),
});

describe('@claude-flow/mcp', () => {
  describe('Module exports', () => {
    it('should export VERSION', () => {
      expect(VERSION).toBe('3.0.0');
    });

    it('should export MODULE_NAME', () => {
      expect(MODULE_NAME).toBe('@claude-flow/mcp');
    });

    it('should export ErrorCodes', () => {
      expect(ErrorCodes.PARSE_ERROR).toBe(-32700);
      expect(ErrorCodes.INVALID_REQUEST).toBe(-32600);
      expect(ErrorCodes.METHOD_NOT_FOUND).toBe(-32601);
      expect(ErrorCodes.INTERNAL_ERROR).toBe(-32603);
    });
  });

  describe('MCPServerError', () => {
    it('should create error with code', () => {
      const error = new MCPServerError('Test error', ErrorCodes.INVALID_REQUEST);
      expect(error.message).toBe('Test error');
      expect(error.code).toBe(ErrorCodes.INVALID_REQUEST);
      expect(error.name).toBe('MCPServerError');
    });

    it('should convert to MCP error format', () => {
      const error = new MCPServerError('Test error', ErrorCodes.PARSE_ERROR, { extra: 'data' });
      const mcpError = error.toMCPError();
      expect(mcpError.code).toBe(ErrorCodes.PARSE_ERROR);
      expect(mcpError.message).toBe('Test error');
      expect(mcpError.data).toEqual({ extra: 'data' });
    });
  });

  describe('ToolRegistry', () => {
    let registry: ReturnType<typeof createToolRegistry>;
    let logger: ILogger;

    beforeEach(() => {
      logger = createMockLogger();
      registry = createToolRegistry(logger);
    });

    it('should register a tool', () => {
      const tool: MCPTool = {
        name: 'test-tool',
        description: 'A test tool',
        inputSchema: { type: 'object', properties: {} },
        handler: async () => ({ result: 'success' }),
      };

      const result = registry.register(tool);
      expect(result).toBe(true);
      expect(registry.hasTool('test-tool')).toBe(true);
      expect(registry.getToolCount()).toBe(1);
    });

    it('should not register duplicate tools', () => {
      const tool: MCPTool = {
        name: 'test-tool',
        description: 'A test tool',
        inputSchema: { type: 'object', properties: {} },
        handler: async () => ({ result: 'success' }),
      };

      registry.register(tool);
      const result = registry.register(tool);
      expect(result).toBe(false);
      expect(registry.getToolCount()).toBe(1);
    });

    it('should override tool with option', () => {
      const tool1: MCPTool = {
        name: 'test-tool',
        description: 'First version',
        inputSchema: { type: 'object', properties: {} },
        handler: async () => ({ result: 'v1' }),
      };

      const tool2: MCPTool = {
        name: 'test-tool',
        description: 'Second version',
        inputSchema: { type: 'object', properties: {} },
        handler: async () => ({ result: 'v2' }),
      };

      registry.register(tool1);
      const result = registry.register(tool2, { override: true });
      expect(result).toBe(true);
      expect(registry.getTool('test-tool')?.description).toBe('Second version');
    });

    it('should unregister a tool', () => {
      const tool: MCPTool = {
        name: 'test-tool',
        description: 'A test tool',
        inputSchema: { type: 'object', properties: {} },
        handler: async () => ({ result: 'success' }),
      };

      registry.register(tool);
      const result = registry.unregister('test-tool');
      expect(result).toBe(true);
      expect(registry.hasTool('test-tool')).toBe(false);
    });

    it('should execute a tool', async () => {
      const tool: MCPTool = {
        name: 'test-tool',
        description: 'A test tool',
        inputSchema: { type: 'object', properties: {} },
        handler: async (input: unknown) => ({ received: input }),
      };

      registry.register(tool);
      const result = await registry.execute('test-tool', { test: 'data' });
      expect(result.isError).toBe(false);
      expect(result.content[0].type).toBe('text');
    });

    it('should return error for unknown tool', async () => {
      const result = await registry.execute('unknown-tool', {});
      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Tool not found');
    });

    it('should filter by category', () => {
      registry.register({
        name: 'tool-a',
        description: 'Tool A',
        inputSchema: { type: 'object' },
        handler: async () => ({}),
        category: 'category-1',
      });

      registry.register({
        name: 'tool-b',
        description: 'Tool B',
        inputSchema: { type: 'object' },
        handler: async () => ({}),
        category: 'category-2',
      });

      const tools = registry.getByCategory('category-1');
      expect(tools.length).toBe(1);
      expect(tools[0].name).toBe('tool-a');
    });

    it('should get stats', () => {
      registry.register({
        name: 'tool-a',
        description: 'Tool A',
        inputSchema: { type: 'object' },
        handler: async () => ({}),
        category: 'cat',
        tags: ['tag1', 'tag2'],
      });

      const stats = registry.getStats();
      expect(stats.totalTools).toBe(1);
      expect(stats.totalCategories).toBe(1);
      expect(stats.totalTags).toBe(2);
    });
  });

  describe('defineTool', () => {
    it('should create a tool definition', () => {
      const tool = defineTool(
        'my-tool',
        'My tool description',
        { type: 'object', properties: { input: { type: 'string' } } },
        async (input) => ({ result: input }),
        { category: 'test', tags: ['tag1'] }
      );

      expect(tool.name).toBe('my-tool');
      expect(tool.description).toBe('My tool description');
      expect(tool.category).toBe('test');
      expect(tool.tags).toEqual(['tag1']);
    });
  });

  describe('SessionManager', () => {
    let manager: ReturnType<typeof createSessionManager>;
    let logger: ILogger;

    beforeEach(() => {
      logger = createMockLogger();
      manager = createSessionManager(logger, { sessionTimeout: 1000 });
    });

    afterEach(() => {
      manager.destroy();
    });

    it('should create a session', () => {
      const session = manager.createSession('stdio');
      expect(session.id).toBeDefined();
      expect(session.state).toBe('created');
      expect(session.transport).toBe('stdio');
      expect(session.isInitialized).toBe(false);
    });

    it('should initialize a session', () => {
      const session = manager.createSession('stdio');
      // protocolVersion is a YYYY-MM-DD spec string per the MCP lifecycle
      // spec — not a {major,minor,patch} object. The earlier object form
      // was rejected by Claude Code's Zod validator (#1874).
      manager.initializeSession(session.id, {
        protocolVersion: '2024-11-05',
        capabilities: { tools: { listChanged: true } },
        clientInfo: { name: 'test-client', version: '1.0.0' },
      });

      const updated = manager.getSession(session.id);
      expect(updated?.isInitialized).toBe(true);
      expect(updated?.state).toBe('ready');
      expect(updated?.clientInfo?.name).toBe('test-client');
    });

    it('should close a session', () => {
      const session = manager.createSession('stdio');
      const result = manager.closeSession(session.id, 'test reason');
      expect(result).toBe(true);
      expect(manager.getSession(session.id)).toBeUndefined();
    });

    it('should get session metrics', () => {
      manager.createSession('stdio');
      manager.createSession('http');

      const metrics = manager.getSessionMetrics();
      expect(metrics.total).toBe(2);
    });

    it('should update session activity', () => {
      const session = manager.createSession('stdio');
      const originalTime = session.lastActivityAt;

      // Small delay to ensure time difference
      const result = manager.updateActivity(session.id);
      expect(result).toBe(true);
    });
  });

  describe('ConnectionPool', () => {
    let pool: ReturnType<typeof createConnectionPool>;
    let logger: ILogger;

    beforeEach(() => {
      logger = createMockLogger();
      pool = createConnectionPool(
        { maxConnections: 5, minConnections: 0, idleTimeout: 100, evictionRunInterval: 60000 },
        logger,
        'in-process'
      );
    });

    afterEach(async () => {
      // Release all connections first to allow fast drain
      for (const conn of pool.getConnections()) {
        if (conn.state === 'busy') {
          pool.release(conn);
        }
      }
      await pool.clear();
    }, 5000);

    it('should acquire a connection', async () => {
      const connection = await pool.acquire();
      expect(connection.id).toBeDefined();
      expect(connection.state).toBe('busy');
    });

    it('should release a connection', async () => {
      const connection = await pool.acquire();
      pool.release(connection);

      const stats = pool.getStats();
      expect(stats.idleConnections).toBeGreaterThan(0);
    });

    it('should get stats', async () => {
      const connection = await pool.acquire();
      const stats = pool.getStats();

      expect(stats.totalConnections).toBeGreaterThan(0);
      expect(stats.totalAcquired).toBe(1);

      // Release for cleanup
      pool.release(connection);
    });

    it('should check health', () => {
      // Pool is healthy when not shutting down and has >= minConnections (0 in this test)
      expect(pool.isHealthy()).toBe(true);
    });
  });

  describe('MCPServer', () => {
    let server: ReturnType<typeof createMCPServer>;
    let logger: ILogger;

    beforeEach(() => {
      logger = createMockLogger();
      server = createMCPServer(
        { name: 'Test Server', transport: 'in-process' },
        logger
      );
    });

    afterEach(async () => {
      await server.stop();
    });

    it('should create server with config', () => {
      expect(server).toBeDefined();
    });

    it('should register a tool', () => {
      const result = server.registerTool({
        name: 'test-tool',
        description: 'Test tool',
        inputSchema: { type: 'object' },
        handler: async () => ({}),
      });
      expect(result).toBe(true);
    });

    it('should register multiple tools', () => {
      const result = server.registerTools([
        {
          name: 'tool-1',
          description: 'Tool 1',
          inputSchema: { type: 'object' },
          handler: async () => ({}),
        },
        {
          name: 'tool-2',
          description: 'Tool 2',
          inputSchema: { type: 'object' },
          handler: async () => ({}),
        },
      ]);
      expect(result.registered).toBe(2);
      expect(result.failed).toHaveLength(0);
    });

    it('should start and stop', async () => {
      await server.start();
      const health = await server.getHealthStatus();
      expect(health.healthy).toBe(true);

      await server.stop();
      const healthAfter = await server.getHealthStatus();
      expect(healthAfter.healthy).toBe(false);
    });

    it('should get metrics', async () => {
      await server.start();
      const metrics = server.getMetrics();

      expect(metrics.totalRequests).toBeDefined();
      expect(metrics.activeSessions).toBeDefined();
    });

    it('should expose resource registry', () => {
      const registry = server.getResourceRegistry();
      expect(registry).toBeDefined();
      expect(typeof registry.registerResource).toBe('function');
    });

    it('should expose prompt registry', () => {
      const registry = server.getPromptRegistry();
      expect(registry).toBeDefined();
      expect(typeof registry.register).toBe('function');
    });

    it('should expose task manager', () => {
      const taskManager = server.getTaskManager();
      expect(taskManager).toBeDefined();
      expect(typeof taskManager.createTask).toBe('function');
    });
  });

  // ============================================================================
  // MCP 2025-11-25 Features
  // ============================================================================

  describe('ResourceRegistry (MCP 2025-11-25)', () => {
    let registry: ReturnType<typeof createResourceRegistry>;
    let logger: ILogger;

    beforeEach(() => {
      logger = createMockLogger();
      registry = createResourceRegistry(logger);
    });

    it('should register a resource', () => {
      const { resource, handler } = createTextResource(
        'file://test.txt',
        'Test File',
        'Hello World'
      );

      const result = registry.registerResource(resource, handler);
      expect(result).toBe(true);
      expect(registry.hasResource('file://test.txt')).toBe(true);
    });

    it('should list resources with pagination', () => {
      for (let i = 0; i < 5; i++) {
        const { resource, handler } = createTextResource(
          `file://test${i}.txt`,
          `Test File ${i}`,
          `Content ${i}`
        );
        registry.registerResource(resource, handler);
      }

      const result = registry.list(undefined, 3);
      expect(result.resources.length).toBe(3);
      expect(result.nextCursor).toBeDefined();

      const nextResult = registry.list(result.nextCursor, 3);
      expect(nextResult.resources.length).toBe(2);
    });

    it('should read resource content', async () => {
      const { resource, handler } = createTextResource(
        'file://test.txt',
        'Test File',
        'Hello World'
      );
      registry.registerResource(resource, handler);

      const result = await registry.read('file://test.txt');
      expect(result.contents[0].text).toBe('Hello World');
    });

    it('should subscribe to resource updates', () => {
      const { resource, handler } = createTextResource(
        'file://test.txt',
        'Test File',
        'Hello World'
      );
      registry.registerResource(resource, handler);

      const callback = vi.fn();
      const subscriptionId = registry.subscribe('file://test.txt', callback);
      expect(subscriptionId).toBeDefined();
      expect(registry.getSubscriptionCount('file://test.txt')).toBe(1);
    });

    it('should get stats', () => {
      const { resource, handler } = createTextResource(
        'file://test.txt',
        'Test File',
        'Hello World'
      );
      registry.registerResource(resource, handler);

      const stats = registry.getStats();
      expect(stats.totalResources).toBe(1);
    });
  });

  describe('PromptRegistry (MCP 2025-11-25)', () => {
    let registry: ReturnType<typeof createPromptRegistry>;
    let logger: ILogger;

    beforeEach(() => {
      logger = createMockLogger();
      registry = createPromptRegistry(logger);
    });

    it('should register a prompt', () => {
      const prompt = definePrompt(
        'code_review',
        'Review code for quality',
        async (args) => [textMessage('user', `Review: ${args.code}`)],
        { arguments: [{ name: 'code', required: true }] }
      );

      const result = registry.register(prompt);
      expect(result).toBe(true);
      expect(registry.hasPrompt('code_review')).toBe(true);
    });

    it('should list prompts with pagination', () => {
      for (let i = 0; i < 5; i++) {
        const prompt = definePrompt(
          `prompt_${i}`,
          `Prompt ${i}`,
          async () => [textMessage('user', `Message ${i}`)]
        );
        registry.register(prompt);
      }

      const result = registry.list(undefined, 3);
      expect(result.prompts.length).toBe(3);
      expect(result.nextCursor).toBeDefined();
    });

    it('should get prompt with arguments', async () => {
      const prompt = definePrompt(
        'greeting',
        'Greet a user',
        async (args) => [textMessage('user', `Hello, ${args.name}!`)],
        { arguments: [{ name: 'name', required: true }] }
      );
      registry.register(prompt);

      const result = await registry.get('greeting', { name: 'World' });
      expect(result.messages[0].content.type).toBe('text');
      expect((result.messages[0].content as any).text).toBe('Hello, World!');
    });

    it('should validate required arguments', async () => {
      const prompt = definePrompt(
        'greeting',
        'Greet a user',
        async (args) => [textMessage('user', `Hello, ${args.name}!`)],
        { arguments: [{ name: 'name', required: true }] }
      );
      registry.register(prompt);

      await expect(registry.get('greeting', {})).rejects.toThrow('Missing required argument');
    });

    it('should interpolate template strings', () => {
      const template = 'Hello, {name}! Your score is {score}.';
      const result = interpolate(template, { name: 'Alice', score: '100' });
      expect(result).toBe('Hello, Alice! Your score is 100.');
    });
  });

  describe('TaskManager (MCP 2025-11-25)', () => {
    let taskManager: ReturnType<typeof createTaskManager>;
    let logger: ILogger;

    beforeEach(() => {
      logger = createMockLogger();
      taskManager = createTaskManager(logger, { cleanupInterval: 60000 });
    });

    afterEach(() => {
      taskManager.destroy();
    });

    it('should create a task', () => {
      const taskId = taskManager.createTask(async (reportProgress, signal) => {
        return { result: 'success' };
      });

      expect(taskId).toBeDefined();
      expect(taskId.startsWith('task-')).toBe(true);
    });

    it('should get task status', () => {
      const taskId = taskManager.createTask(async () => 'done');
      const status = taskManager.getTask(taskId);

      expect(status).toBeDefined();
      expect(status?.taskId).toBe(taskId);
    });

    it('should track task progress', async () => {
      const taskId = taskManager.createTask(async (reportProgress) => {
        reportProgress({ progress: 50, total: 100, message: 'Halfway' });
        return 'done';
      });

      // Wait for task to complete
      const result = await taskManager.waitForTask(taskId, 5000);
      expect(result.state).toBe('completed');
    });

    it('should cancel a task', () => {
      const taskId = taskManager.createTask(async (reportProgress, signal) => {
        // Long running task
        await new Promise((resolve, reject) => {
          const timeout = setTimeout(resolve, 10000);
          signal.addEventListener('abort', () => {
            clearTimeout(timeout);
            reject(new Error('Cancelled'));
          });
        });
      });

      const cancelled = taskManager.cancelTask(taskId, 'User requested');
      expect(cancelled).toBe(true);
    });

    it('should get all tasks', () => {
      taskManager.createTask(async () => 'task1');
      taskManager.createTask(async () => 'task2');

      const tasks = taskManager.getAllTasks();
      expect(tasks.length).toBe(2);
    });

    it('should get stats', () => {
      taskManager.createTask(async () => 'done');

      const stats = taskManager.getStats();
      expect(stats.totalTasks).toBeGreaterThan(0);
    });
  });
});
