import { describe, it, expect, vi } from 'vitest';
import { McpdUpstream } from '../src/upstream/mcpd.js';
import type { JsonRpcRequest } from '../src/types.js';

function mockMcpdClient(responses: Map<string, unknown> = new Map()) {
  return {
    baseUrl: 'http://test:3100',
    token: 'test-token',
    get: vi.fn(),
    post: vi.fn(async (_path: string, body: unknown) => {
      const req = body as { serverId: string; method: string };
      const key = `${req.serverId}:${req.method}`;
      if (responses.has(key)) {
        return responses.get(key);
      }
      return { result: { ok: true } };
    }),
    put: vi.fn(),
    delete: vi.fn(),
    forward: vi.fn(),
  };
}

describe('McpdUpstream', () => {
  it('sends tool calls via mcpd proxy', async () => {
    const client = mockMcpdClient(new Map([
      ['srv-1:tools/call', { result: { content: [{ type: 'text', text: 'hello' }] } }],
    ]));

    const upstream = new McpdUpstream('srv-1', 'slack', client as any);
    const request: JsonRpcRequest = {
      jsonrpc: '2.0',
      id: '1',
      method: 'tools/call',
      params: { name: 'search', arguments: { query: 'test' } },
    };

    const response = await upstream.send(request);
    expect(response.result).toEqual({ content: [{ type: 'text', text: 'hello' }] });
    expect(client.post).toHaveBeenCalledWith('/api/v1/mcp/proxy', {
      serverId: 'srv-1',
      method: 'tools/call',
      params: { name: 'search', arguments: { query: 'test' } },
    });
  });

  it('sends tools/list via mcpd proxy', async () => {
    const client = mockMcpdClient(new Map([
      ['srv-1:tools/list', { result: { tools: [{ name: 'search', description: 'Search' }] } }],
    ]));

    const upstream = new McpdUpstream('srv-1', 'slack', client as any);
    const request: JsonRpcRequest = {
      jsonrpc: '2.0',
      id: '2',
      method: 'tools/list',
    };

    const response = await upstream.send(request);
    expect(response.result).toEqual({ tools: [{ name: 'search', description: 'Search' }] });
  });

  it('returns error when mcpd fails', async () => {
    const client = mockMcpdClient();
    client.post.mockRejectedValue(new Error('connection refused'));

    const upstream = new McpdUpstream('srv-1', 'slack', client as any);
    const request: JsonRpcRequest = { jsonrpc: '2.0', id: '3', method: 'tools/list' };

    const response = await upstream.send(request);
    expect(response.error).toBeDefined();
    expect(response.error!.message).toContain('mcpd proxy error');
  });

  it('returns error when upstream is closed', async () => {
    const client = mockMcpdClient();
    const upstream = new McpdUpstream('srv-1', 'slack', client as any);
    await upstream.close();

    const request: JsonRpcRequest = { jsonrpc: '2.0', id: '4', method: 'tools/list' };
    const response = await upstream.send(request);
    expect(response.error).toBeDefined();
    expect(response.error!.message).toContain('closed');
  });

  it('reports alive status correctly', async () => {
    const client = mockMcpdClient();
    const upstream = new McpdUpstream('srv-1', 'slack', client as any);
    expect(upstream.isAlive()).toBe(true);
    await upstream.close();
    expect(upstream.isAlive()).toBe(false);
  });

  it('relays error responses from mcpd', async () => {
    const client = mockMcpdClient(new Map([
      ['srv-1:tools/call', { error: { code: -32601, message: 'Tool not found' } }],
    ]));

    const upstream = new McpdUpstream('srv-1', 'slack', client as any);
    const request: JsonRpcRequest = {
      jsonrpc: '2.0',
      id: '5',
      method: 'tools/call',
      params: { name: 'nonexistent' },
    };

    const response = await upstream.send(request);
    expect(response.error).toEqual({ code: -32601, message: 'Tool not found' });
  });
});
