/**
 * @fileoverview Integration tests for the registry-based MCP server implementation
 */

import { vi } from 'vitest';

// Mock MCP SDK modules FIRST using unstable_mockModule
vi.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({
  McpServer: vi.fn().mockImplementation(() => ({
    registerTool: vi.fn(),
    connect: vi.fn().mockResolvedValue(undefined),
  })),
}));

vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({
  StdioServerTransport: vi.fn().mockImplementation(() => ({})),
}));

// Mock logger
vi.mock('../utils/logging/logger.js', () => ({
  createLogger: vi.fn(() => ({
    info: vi.fn(),
    debug: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
  })),
}));

// Mock the handlers
vi.mock('../handlers/index.js', () => ({
  handleProjects: vi.fn().mockResolvedValue({
    content: [{ type: 'text', text: JSON.stringify([{ key: 'test', name: 'Test Project' }]) }],
  }),
  handleDeepsourceQualityMetrics: vi.fn().mockResolvedValue({
    content: [{ type: 'text', text: JSON.stringify({ metrics: [] }) }],
  }),
  handleDeepsourceUpdateMetricThreshold: vi.fn().mockResolvedValue({
    content: [{ type: 'text', text: JSON.stringify({ ok: true }) }],
  }),
  handleDeepsourceUpdateMetricSetting: vi.fn().mockResolvedValue({
    content: [{ type: 'text', text: JSON.stringify({ success: true }) }],
  }),
  handleDeepsourceComplianceReport: vi.fn().mockResolvedValue({
    content: [{ type: 'text', text: JSON.stringify({ report: {} }) }],
  }),
  handleDeepsourceProjectIssues: vi.fn().mockResolvedValue({
    content: [{ type: 'text', text: JSON.stringify({ issues: [] }) }],
  }),
  handleDeepsourceProjectRuns: vi.fn().mockResolvedValue({
    content: [{ type: 'text', text: JSON.stringify({ runs: [] }) }],
  }),
  handleDeepsourceRun: vi.fn().mockResolvedValue({
    content: [{ type: 'text', text: JSON.stringify({ run: {} }) }],
  }),
  handleDeepsourceRecentRunIssues: vi.fn().mockResolvedValue({
    content: [{ type: 'text', text: JSON.stringify({ issues: [] }) }],
  }),
  handleDeepsourceDependencyVulnerabilities: vi.fn().mockResolvedValue({
    content: [{ type: 'text', text: JSON.stringify({ vulnerabilities: [] }) }],
  }),
}));

// Mock the server/tool-helpers
vi.mock('../server/tool-helpers.js', () => ({
  logToolInvocation: vi.fn(),
  logToolResult: vi.fn(),
  logAndFormatError: vi.fn().mockReturnValue('Formatted error'),
}));

// Mock the handler-adapters
vi.mock('../adapters/handler-adapters.js', () => ({
  adaptQualityMetricsParams: vi.fn((params) => params),
  adaptUpdateMetricThresholdParams: vi.fn((params) => params),
  adaptComplianceReportParams: vi.fn((params) => params),
  adaptProjectIssuesParams: vi.fn((params) => params),
}));

// Mock zod for schema validation
vi.mock('zod', () => ({
  z: {
    object: vi.fn(() => ({ safeParse: vi.fn(() => ({ success: true, data: {} })) })),
    string: vi.fn(() => ({})),
    number: vi.fn(() => ({})),
    boolean: vi.fn(() => ({})),
    array: vi.fn(() => ({})),
    optional: vi.fn(() => ({})),
  },
}));

// Mock handler base modules
vi.mock('../handlers/base/handler.interface.js', () => ({
  HandlerFunction: {},
  BaseHandlerDeps: {},
}));

vi.mock('../handlers/base/handler.factory.js', () => ({
  createDefaultHandlerDeps: vi.fn(),
  isApiResponse: vi.fn(() => false),
  createErrorResponse: vi.fn((error) => ({
    content: [{ type: 'text', text: `Error: ${error.message}` }],
    isError: true,
  })),
}));

// Mock the ToolRegistry class
class MockToolRegistry {
  private tools: Array<{
    name: string;
    description: string;
    inputSchema?: Record<string, unknown>;
    handler: MockedFunction<(...args: unknown[]) => Promise<unknown>>;
  }> = [];

  registerTool(tool: {
    name: string;
    description: string;
    inputSchema?: Record<string, unknown>;
    handler: MockedFunction<(...args: unknown[]) => Promise<unknown>>;
  }) {
    if (this.tools.find((t) => t.name === tool.name)) {
      throw new Error(`Tool ${tool.name} is already registered`);
    }
    this.tools.push(tool);
  }

  getTools() {
    return this.tools;
  }

  registerWithMcpServer(server: {
    registerTool: (_config: {
      name: string;
      description: string;
      inputSchema?: unknown;
      handler: unknown;
    }) => void;
  }) {
    this.tools.forEach((tool) => {
      server.registerTool({
        name: tool.name,
        description: tool.description,
        inputSchema: tool.inputSchema,
        handler: tool.handler,
      });
    });
  }
}

vi.mock('../server/tool-registry.js', () => ({
  ToolRegistry: MockToolRegistry,
}));

// Now import test utilities and modules after mocks are set
const { describe, it, expect, beforeEach, afterEach } = await import('vitest');
const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js');
const { ToolRegistry } = await import('../server/tool-registry.js');
const {
  adaptQualityMetricsParams,
  adaptUpdateMetricThresholdParams,
  adaptComplianceReportParams,
  adaptProjectIssuesParams,
} = await import('../adapters/handler-adapters.js');

describe('Registry-based MCP Server Integration', () => {
  let registry: MockToolRegistry;
  let mockMcpServer: {
    registerTool: ReturnType<typeof vi.fn>;
    connect: MockedFunction<() => Promise<void>>;
  };

  beforeEach(() => {
    // Create fresh instances for each test
    mockMcpServer = {
      registerTool: vi.fn(),
      connect: vi.fn().mockResolvedValue(undefined),
    };
    const MockedMcpServer = McpServer as ReturnType<typeof vi.fn>;
    MockedMcpServer.mockImplementation(() => mockMcpServer as ReturnType<typeof McpServer>);
    registry = new ToolRegistry();
    vi.clearAllMocks();
  });

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

  describe('ToolRegistry', () => {
    it('should register tools successfully', () => {
      const tool = {
        name: 'test-tool',
        description: 'A test tool',
        inputSchema: {
          type: 'object',
          properties: {
            param: { type: 'string' },
          },
        },
        handler: vi.fn().mockResolvedValue({ success: true }),
      };

      registry.registerTool(tool);
      const tools = registry.getTools();

      expect(tools).toHaveLength(1);
      expect(tools[0].name).toBe('test-tool');
    });

    it('should prevent duplicate tool registration', () => {
      const tool = {
        name: 'test-tool',
        description: 'A test tool',
        handler: vi.fn(),
      };

      registry.registerTool(tool);

      expect(() => registry.registerTool(tool)).toThrow('Tool test-tool is already registered');
    });

    it('should register tools with MCP server', () => {
      const server = new McpServer('test-server', '1.0.0') as ReturnType<typeof McpServer>;
      const tool = {
        name: 'test-tool',
        description: 'A test tool',
        handler: vi.fn(),
      };

      registry.registerTool(tool);
      registry.registerWithMcpServer(server);

      expect(server.registerTool).toHaveBeenCalledWith({
        name: 'test-tool',
        description: 'A test tool',
        inputSchema: undefined,
        handler: expect.any(Function),
      });
    });
  });

  describe('Handler Adapter Integration', () => {
    it('should correctly adapt quality metrics parameters', () => {
      const params = {
        project_key: 'test-project',
        from_date: '2024-01-01',
        to_date: '2024-01-31',
      };

      const adapted = adaptQualityMetricsParams(params);
      expect(adapted).toEqual(params);
      expect(adaptQualityMetricsParams).toHaveBeenCalledWith(params);
    });

    it('should correctly adapt update metric threshold parameters', () => {
      const params = {
        project_key: 'test-project',
        metric_key: 'coverage',
        threshold: 80,
      };

      const adapted = adaptUpdateMetricThresholdParams(params);
      expect(adapted).toEqual(params);
      expect(adaptUpdateMetricThresholdParams).toHaveBeenCalledWith(params);
    });

    it('should correctly adapt compliance report parameters', () => {
      const params = {
        project_key: 'test-project',
        framework: 'OWASP',
      };

      const adapted = adaptComplianceReportParams(params);
      expect(adapted).toEqual(params);
      expect(adaptComplianceReportParams).toHaveBeenCalledWith(params);
    });

    it('should correctly adapt project issues parameters', () => {
      const params = {
        project_key: 'test-project',
        page: 1,
        limit: 20,
        category: 'bug',
      };

      const adapted = adaptProjectIssuesParams(params);
      expect(adapted).toEqual(params);
      expect(adaptProjectIssuesParams).toHaveBeenCalledWith(params);
    });
  });

  describe('End-to-End Tool Registration', () => {
    it('should handle successful tool execution', async () => {
      const handler = vi.fn().mockResolvedValue({
        content: [{ type: 'text', text: 'Success' }],
      });

      const tool = {
        name: 'test-tool',
        description: 'A test tool',
        inputSchema: {
          type: 'object',
          properties: {
            param: { type: 'string' },
          },
          required: ['param'],
        },
        handler,
      };

      registry.registerTool(tool);
      const registeredTool = registry.getTools()[0];

      // Call the wrapped handler
      const result = await registeredTool.handler({ param: 'test' });

      expect(handler).toHaveBeenCalledWith({ param: 'test' });
      expect(result).toEqual({
        content: [{ type: 'text', text: 'Success' }],
      });
    });

    it('should handle tool execution errors', async () => {
      const handler = vi.fn().mockRejectedValue(new Error('Tool error'));

      const tool = {
        name: 'test-tool',
        description: 'A test tool',
        handler,
      };

      registry.registerTool(tool);
      const registeredTool = registry.getTools()[0];

      // Call the wrapped handler
      await expect(registeredTool.handler({})).rejects.toThrow('Tool error');
    });
  });

  describe('Schema Validation', () => {
    it('should validate input schema when provided', async () => {
      const handler = vi.fn().mockResolvedValue({ success: true });

      const tool = {
        name: 'test-tool',
        description: 'A test tool',
        inputSchema: {
          type: 'object',
          properties: {
            required_param: { type: 'string' },
          },
          required: ['required_param'],
        },
        handler,
      };

      registry.registerTool(tool);
      const registeredTool = registry.getTools()[0];

      // This will be validated by the registry's wrapper
      await registeredTool.handler({ required_param: 'value' });

      expect(handler).toHaveBeenCalledWith({ required_param: 'value' });
    });
  });
});
