import { describe, it, expect, vi } from 'vitest';
import { ToolRegistry } from './registry.js';
import type { Tool } from './types.js';

const echoTool: Tool = {
  name: 'test.echo',
  description: 'Echoes input back',
  inputSchema: {
    type: 'object',
    properties: { text: { type: 'string', description: 'Text to echo' } },
    required: ['text'],
  },
  execute: async (args) => ({ success: true, output: String((args as { text: string }).text) }),
};

const greetTool: Tool = {
  name: 'test.greet',
  description: 'Greets someone',
  inputSchema: {
    type: 'object',
    properties: { name: { type: 'string' } },
    required: ['name'],
  },
  execute: async (args) => ({ success: true, output: `Hello ${(args as { name: string }).name}` }),
};

describe('ToolRegistry', () => {
  it('registers and retrieves tools by name', () => {
    const registry = new ToolRegistry();
    registry.register(echoTool);

    expect(registry.get('test.echo')).toBe(echoTool);
    expect(registry.get('nonexistent')).toBeUndefined();
  });

  it('lists all registered tools', () => {
    const registry = new ToolRegistry();
    registry.register(echoTool);
    registry.register(greetTool);

    const tools = registry.list();
    expect(tools).toHaveLength(2);
    expect(tools.map(t => t.name)).toContain('test.echo');
    expect(tools.map(t => t.name)).toContain('test.greet');
  });

  it('throws on duplicate registration', () => {
    const registry = new ToolRegistry();
    registry.register(echoTool);
    expect(() => registry.register(echoTool)).toThrow('already registered');
  });

  it('serializes to Anthropic format', () => {
    const registry = new ToolRegistry();
    registry.register(echoTool);

    const anthropicTools = registry.toAnthropicFormat();
    expect(anthropicTools).toEqual([{
      name: 'test_echo',
      description: 'Echoes input back',
      input_schema: echoTool.inputSchema,
    }]);
  });

  it('unregisters a tool by name', () => {
    const registry = new ToolRegistry();
    registry.register(echoTool);
    registry.register(greetTool);

    expect(registry.unregister('test.echo')).toBe(true);
    expect(registry.get('test.echo')).toBeUndefined();
    expect(registry.list()).toHaveLength(1);
    expect(registry.list()[0].name).toBe('test.greet');
  });

  it('returns false when unregistering a nonexistent tool', () => {
    const registry = new ToolRegistry();
    expect(registry.unregister('nonexistent')).toBe(false);
  });

  it('serializes to OpenAI format', () => {
    const registry = new ToolRegistry();
    registry.register(echoTool);

    const openaiTools = registry.toOpenAIFormat();
    expect(openaiTools).toEqual([{
      type: 'function',
      function: {
        name: 'test_echo',
        description: 'Echoes input back',
        parameters: echoTool.inputSchema,
      },
    }]);
  });

  describe('ToolRegistry — clone()', () => {
    function makeTool(name: string): Tool {
      return {
        name,
        description: `Mock ${name}`,
        inputSchema: { type: 'object', properties: {} },
        execute: async () => ({ success: true, output: '' }),
      };
    }

    it('creates a copy with all tools', () => {
      const reg = new ToolRegistry();
      reg.register(makeTool('tool.a'));
      reg.register(makeTool('tool.b'));

      const cloned = reg.clone();
      expect(cloned.list().map(t => t.name).sort()).toEqual(['tool.a', 'tool.b']);
    });

    it('inherits the policy from original', () => {
      const reg = new ToolRegistry();
      const mockPolicy = { filterTools: vi.fn(), isAllowed: vi.fn(), resolveAllowedNames: vi.fn(), getEffectiveProfile: vi.fn() };
      reg.setPolicy(mockPolicy as any);

      const cloned = reg.clone();
      expect(cloned.getPolicy()).toBe(mockPolicy);
    });

    it('allows replacing tools in clone without affecting original', () => {
      const reg = new ToolRegistry();
      const originalTool = makeTool('shell.exec');
      reg.register(originalTool);

      const cloned = reg.clone();
      const replacementTool = makeTool('shell.exec');
      replacementTool.description = 'Sandboxed version';

      cloned.replace(replacementTool);
      expect(cloned.get('shell.exec')!.description).toBe('Sandboxed version');
      expect(reg.get('shell.exec')!.description).toBe('Mock shell.exec');
    });
  });

  describe('ToolRegistry — replace()', () => {
    function makeTool(name: string): Tool {
      return {
        name,
        description: `Mock ${name}`,
        inputSchema: { type: 'object', properties: {} },
        execute: async () => ({ success: true, output: '' }),
      };
    }

    it('replaces an existing tool', () => {
      const reg = new ToolRegistry();
      reg.register(makeTool('tool.a'));
      const replacement = makeTool('tool.a');
      replacement.description = 'New description';

      reg.replace(replacement);
      expect(reg.get('tool.a')!.description).toBe('New description');
    });

    it('throws if tool does not exist', () => {
      const reg = new ToolRegistry();
      expect(() => reg.replace(makeTool('nonexistent'))).toThrow('not registered');
    });
  });

  describe('ToolRegistry — API name sanitization', () => {
    it('sanitizeToolName converts dots to underscores', () => {
      expect(ToolRegistry.sanitizeToolName('shell.exec')).toBe('shell_exec');
      expect(ToolRegistry.sanitizeToolName('file.read')).toBe('file_read');
      expect(ToolRegistry.sanitizeToolName('no_dots')).toBe('no_dots');
    });

    it('getByApiName resolves sanitized names back to internal tools', () => {
      const registry = new ToolRegistry();
      registry.register(echoTool); // name is 'test.echo'

      expect(registry.getByApiName('test_echo')).toBe(echoTool);
      expect(registry.getByApiName('test.echo')).toBe(echoTool);
      expect(registry.getByApiName('nonexistent')).toBeUndefined();
    });

    it('toAnthropicFormat outputs sanitized names', () => {
      const registry = new ToolRegistry();
      registry.register(echoTool);

      const tools = registry.toAnthropicFormat();
      expect(tools[0].name).toBe('test_echo');
    });

    it('toOpenAIFormat outputs sanitized names', () => {
      const registry = new ToolRegistry();
      registry.register(echoTool);

      const tools = registry.toOpenAIFormat();
      expect(tools[0].function.name).toBe('test_echo');
    });
  });
});
