import { describe, it, expect, vi } from 'vitest';
import { mcpToolName, parseMcpToolName, bridgeMcpTool, bridgeAllTools } from './bridge.js';
import type { McpClient } from './client.js';
import type { McpToolInfo } from './types.js';

describe('mcpToolName', () => {
  it('creates prefixed tool name', () => {
    expect(mcpToolName('filesystem', 'read_file')).toBe('mcp:filesystem:read_file');
  });

  it('handles server names with hyphens', () => {
    expect(mcpToolName('my-server', 'do_thing')).toBe('mcp:my-server:do_thing');
  });
});

describe('parseMcpToolName', () => {
  it('parses a valid mcp tool name', () => {
    expect(parseMcpToolName('mcp:filesystem:read_file')).toEqual({
      serverName: 'filesystem',
      toolName: 'read_file',
    });
  });

  it('handles tool names containing colons', () => {
    expect(parseMcpToolName('mcp:server:ns:tool')).toEqual({
      serverName: 'server',
      toolName: 'ns:tool',
    });
  });

  it('returns null for non-mcp tool names', () => {
    expect(parseMcpToolName('shell.exec')).toBeNull();
    expect(parseMcpToolName('file.read')).toBeNull();
  });

  it('returns null for malformed mcp names', () => {
    expect(parseMcpToolName('mcp:')).toBeNull();
    expect(parseMcpToolName('mcp:server')).toBeNull();
  });
});

describe('bridgeMcpTool', () => {
  const toolInfo: McpToolInfo = {
    name: 'read_file',
    description: 'Read a file from disk',
    inputSchema: {
      type: 'object',
      properties: { path: { type: 'string' } },
      required: ['path'],
    },
  };

  function createMockClient(
    callResult = { content: 'file contents', isError: false },
  ): McpClient {
    return {
      serverName: 'filesystem',
      tools: [toolInfo],
      status: 'connected',
      callTool: vi.fn().mockResolvedValue(callResult),
    } as unknown as McpClient;
  }

  it('creates a tool with prefixed name', () => {
    const client = createMockClient();
    const tool = bridgeMcpTool(client, toolInfo);

    expect(tool.name).toBe('mcp:filesystem:read_file');
  });

  it('includes server name in description', () => {
    const client = createMockClient();
    const tool = bridgeMcpTool(client, toolInfo);

    expect(tool.description).toContain('[MCP:filesystem]');
    expect(tool.description).toContain('Read a file from disk');
  });

  it('preserves input schema', () => {
    const client = createMockClient();
    const tool = bridgeMcpTool(client, toolInfo);

    expect(tool.inputSchema.type).toBe('object');
    expect(tool.inputSchema.properties).toEqual({ path: { type: 'string' } });
    expect(tool.inputSchema.required).toEqual(['path']);
  });

  it('execute calls the MCP client with correct tool name and args', async () => {
    const client = createMockClient();
    const tool = bridgeMcpTool(client, toolInfo);

    const result = await tool.execute({ path: '/tmp/test.txt' });

    expect(client.callTool).toHaveBeenCalledWith('read_file', { path: '/tmp/test.txt' });
    expect(result.success).toBe(true);
    expect(result.output).toBe('file contents');
  });

  it('execute returns error result when MCP server reports error', async () => {
    const client = createMockClient({ content: 'file not found', isError: true });
    const tool = bridgeMcpTool(client, toolInfo);

    const result = await tool.execute({ path: '/nonexistent' });

    expect(result.success).toBe(false);
    expect(result.error).toBe('file not found');
  });

  it('execute catches exceptions from MCP client', async () => {
    const client = {
      serverName: 'filesystem',
      tools: [toolInfo],
      callTool: vi.fn().mockRejectedValue(new Error('connection lost')),
    } as unknown as McpClient;

    const tool = bridgeMcpTool(client, toolInfo);
    const result = await tool.execute({ path: '/tmp/test.txt' });

    expect(result.success).toBe(false);
    expect(result.error).toBe('connection lost');
  });

  it('execute handles null args gracefully', async () => {
    const client = createMockClient();
    const tool = bridgeMcpTool(client, toolInfo);

    await tool.execute(null);

    expect(client.callTool).toHaveBeenCalledWith('read_file', {});
  });
});

describe('bridgeAllTools', () => {
  it('bridges all tools from a client', () => {
    const client = {
      serverName: 'myserver',
      tools: [
        { name: 'tool_a', description: 'A', inputSchema: { type: 'object' as const, properties: {} } },
        { name: 'tool_b', description: 'B', inputSchema: { type: 'object' as const, properties: {} } },
      ],
      callTool: vi.fn(),
    } as unknown as McpClient;

    const tools = bridgeAllTools(client);

    expect(tools).toHaveLength(2);
    expect(tools[0].name).toBe('mcp:myserver:tool_a');
    expect(tools[1].name).toBe('mcp:myserver:tool_b');
  });
});
