/**
 * v1.15.0-mcp-multi: unit tests for the multi-server MCP client.
 * Pure unit tests — no live MCP server needed. Tests tool-wrapping,
 * read-only guard, name prefixing, content extraction, and error handling.
 * Multi-server routing tested via wrapMcpTool's server-name prefix.
 */
import { describe, it, expect } from 'vitest';
import { wrapMcpTool, extractContent, isToolReadOnly } from '../mcp-client.js';

describe('mcp-client', () => {
  describe('wrapMcpTool — multi-server prefixing', () => {
    it('produces a ToolDef with <serverName>_ prefix', () => {
      const mcpTool = {
        name: 'resolve-library-id',
        description: 'Resolve a library identifier',
        inputSchema: {
          type: 'object' as const,
          properties: { query: { type: 'string' } },
          required: ['query'],
        },
      };

      const wrapped = wrapMcpTool('context7', mcpTool);

      expect(wrapped.name).toBe('context7_resolve-library-id');
      expect(wrapped.description).toBe('Resolve a library identifier');
      expect(wrapped.jsonSchema.type).toBe('function');
      expect(wrapped.jsonSchema.function.name).toBe('context7_resolve-library-id');
      expect(wrapped.jsonSchema.function.parameters).toEqual(mcpTool.inputSchema);
      expect(typeof wrapped.execute).toBe('function');
    });

    it('prefixes tools from different servers correctly', () => {
      const toolA = {
        name: 'query-docs',
        description: 'Query docs',
        inputSchema: { type: 'object' as const, properties: {} },
      };
      const toolB = {
        name: 'overview',
        description: 'Get overview',
        inputSchema: { type: 'object' as const, properties: {} },
      };

      const wrappedA = wrapMcpTool('context7', toolA);
      const wrappedB = wrapMcpTool('codecontext', toolB);

      expect(wrappedA.name).toBe('context7_query-docs');
      expect(wrappedB.name).toBe('codecontext_overview');
    });

    it('multi-server: two servers with 2 tools each produce 4 prefixed tools', () => {
      const serverATools = [
        { name: 'query-docs', inputSchema: { type: 'object' as const, properties: {} } },
        { name: 'resolve-library-id', inputSchema: { type: 'object' as const, properties: {} } },
      ];
      const serverBTools = [
        { name: 'overview', inputSchema: { type: 'object' as const, properties: {} } },
        { name: 'search', inputSchema: { type: 'object' as const, properties: {} } },
      ];

      const allWrapped = [
        ...serverATools.map((t) => wrapMcpTool('context7', t)),
        ...serverBTools.map((t) => wrapMcpTool('codecontext', t)),
      ];

      expect(allWrapped).toHaveLength(4);
      expect(allWrapped.map((t) => t.name)).toEqual([
        'context7_query-docs',
        'context7_resolve-library-id',
        'codecontext_overview',
        'codecontext_search',
      ]);
    });

    it('defaults description to empty string when absent', () => {
      const mcpTool = {
        name: 'no-desc',
        inputSchema: { type: 'object' as const, properties: {} },
      };

      const wrapped = wrapMcpTool('myserver', mcpTool);

      expect(wrapped.description).toBe('');
      expect(wrapped.jsonSchema.function.description).toBe('');
    });

    it('uses passthrough Zod schema (z.record)', () => {
      const mcpTool = {
        name: 'test',
        inputSchema: { type: 'object' as const, properties: {} },
      };

      const wrapped = wrapMcpTool('s', mcpTool);

      const result = wrapped.inputSchema.safeParse({ foo: 'bar', baz: 123 });
      expect(result.success).toBe(true);
    });
  });

  describe('isToolReadOnly', () => {
    it('accepts tools with readOnlyHint: true', () => {
      expect(isToolReadOnly({ readOnlyHint: true })).toBe(true);
    });

    it('accepts tools with no annotations', () => {
      expect(isToolReadOnly(undefined)).toBe(true);
    });

    it('accepts tools with empty annotations', () => {
      expect(isToolReadOnly({})).toBe(true);
    });

    it('rejects tools with readOnlyHint: false', () => {
      expect(isToolReadOnly({ readOnlyHint: false })).toBe(false);
    });

    it('accepts tools with only destructiveHint set', () => {
      expect(isToolReadOnly({ destructiveHint: true })).toBe(true);
    });
  });

  describe('extractContent', () => {
    it('extracts single text block', () => {
      const content = [{ type: 'text', text: 'hello world' }];
      expect(extractContent(content)).toBe('hello world');
    });

    it('joins multiple text blocks with newline', () => {
      const content = [
        { type: 'text', text: 'line 1' },
        { type: 'text', text: 'line 2' },
      ];
      expect(extractContent(content)).toBe('line 1\nline 2');
    });

    it('returns "(no output)" for empty content', () => {
      expect(extractContent([])).toBe('(no output)');
    });

    it('returns "(no output)" for undefined content', () => {
      expect(extractContent(undefined)).toBe('(no output)');
    });

    it('serializes non-text blocks as JSON', () => {
      const content = [
        { type: 'resource', uri: 'file:///foo', mimeType: 'text/plain' },
      ];
      const result = extractContent(content);
      expect(result).toContain('"type":"resource"');
      expect(result).toContain('"uri":"file:///foo"');
    });

    it('returns error shape when isError is true', () => {
      const content = [{ type: 'text', text: 'something failed' }];
      const result = extractContent(content, true);
      expect(result).toEqual({ error: true, output: 'something failed' });
    });

    it('returns error shape with joined content on isError', () => {
      const content = [
        { type: 'text', text: 'error 1' },
        { type: 'text', text: 'error 2' },
      ];
      const result = extractContent(content, true);
      expect(result).toEqual({ error: true, output: 'error 1\nerror 2' });
    });
  });
});
