/**
 * @fileoverview Tests for tool registry security validation and edge cases
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { ToolRegistry, ToolDefinition } from '../../server/tool-registry.js';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { BaseHandlerDeps } from '../../handlers/base/handler.interface.js';

// Mock fs module
vi.mock('fs', () => ({
  promises: {
    readdir: vi.fn(),
    stat: vi.fn(),
    access: vi.fn(),
  },
}));

// Mock dynamic import
const mockDynamicImport = vi.fn();
// @ts-expect-error - Override global import for testing
global.import = mockDynamicImport;

// Type for accessing private static methods in tests
type ToolRegistryStatic = typeof ToolRegistry & {
  matchesPattern: (filename: string, patterns: string[]) => boolean;
  passesFilters: (
    tool: { metadata?: { category?: string; tags?: string[] } },
    options: {
      includeCategories?: string[];
      excludeCategories?: string[];
      includeTags?: string[];
      excludeTags?: string[];
    }
  ) => boolean;
};

describe('ToolRegistry Security and Edge Cases', () => {
  // Note: This file now only tests public API methods and static helpers
  // Private method tests have been removed as they are implementation details
  let mockServer: McpServer;
  let mockDeps: BaseHandlerDeps;
  let registry: ToolRegistry;

  beforeEach(() => {
    mockServer = {
      registerTool: vi.fn(),
      connect: vi.fn(),
    } as unknown as McpServer;

    mockDeps = {
      getApiKey: vi.fn(() => 'test-api-key'),
      clientFactory: {} as BaseHandlerDeps['clientFactory'],
      projectRepository: {} as BaseHandlerDeps['projectRepository'],
      analysisRunRepository: {} as BaseHandlerDeps['analysisRunRepository'],
      metricsRepository: {} as BaseHandlerDeps['metricsRepository'],
      complianceReportRepository: {} as BaseHandlerDeps['complianceReportRepository'],
      logger: {
        info: vi.fn(),
        warn: vi.fn(),
        error: vi.fn(),
        debug: vi.fn(),
      },
    };

    registry = new ToolRegistry(mockServer, mockDeps);
    vi.clearAllMocks();
  });

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

  describe('discoverTools - Security and Filtering', () => {
    it('should handle file system errors gracefully', async () => {
      const fs = vi.mocked(await import('fs'));
      fs.promises.readdir.mockRejectedValueOnce(new Error('Permission denied'));

      const result = await registry.discoverTools({
        directories: ['./protected-dir'],
        patterns: ['*.tool.js'],
      });

      expect(result).toEqual([]);
      // The error is caught internally but should return empty array
    });

    it('should return empty array for non-existent directories', async () => {
      const fs = vi.mocked(await import('fs'));
      fs.promises.readdir.mockRejectedValueOnce(new Error('ENOENT: no such file or directory'));

      const result = await registry.discoverTools({
        directories: ['./nonexistent'],
        patterns: ['*.tool.js'],
      });

      expect(result).toEqual([]);
    });
  });

  describe('passesFilters - Edge Cases', () => {
    it('should handle tools without metadata', () => {
      const tool: ToolDefinition = {
        name: 'test-tool',
        description: 'Test tool',
        handler: vi.fn(),
      };

      const passesFilters = (ToolRegistry as unknown as ToolRegistryStatic).passesFilters;

      // Should pass when no filters
      expect(passesFilters(tool, {})).toBe(true);

      // Should fail when category filter exists but tool has no metadata
      expect(passesFilters(tool, { includeCategories: ['api'] })).toBe(false);

      // Should pass when exclude filter exists but tool has no metadata
      expect(passesFilters(tool, { excludeCategories: ['api'] })).toBe(true);
    });

    it('should handle empty filter arrays', () => {
      const tool: ToolDefinition = {
        name: 'test-tool',
        description: 'Test tool',
        handler: vi.fn(),
        metadata: {
          category: 'data',
          tags: ['stable'],
        },
      };

      const passesFilters = (ToolRegistry as unknown as ToolRegistryStatic).passesFilters;

      // Empty arrays should not filter anything
      expect(passesFilters(tool, { includeCategories: [] })).toBe(true);
      expect(passesFilters(tool, { excludeCategories: [] })).toBe(true);
      expect(passesFilters(tool, { includeTags: [] })).toBe(true);
      expect(passesFilters(tool, { excludeTags: [] })).toBe(true);
    });
  });

  describe('matchesPattern - Pattern Matching', () => {
    it('should match exact filenames', () => {
      const matchesPattern = (ToolRegistry as unknown as ToolRegistryStatic).matchesPattern;

      expect(matchesPattern('test.tool.js', ['test.tool.js'])).toBe(true);
      expect(matchesPattern('test.tool.js', ['other.tool.js'])).toBe(false);
    });

    it('should match wildcard patterns', () => {
      const matchesPattern = (ToolRegistry as unknown as ToolRegistryStatic).matchesPattern;

      expect(matchesPattern('test.tool.js', ['*.tool.js'])).toBe(true);
      expect(matchesPattern('test.tool.ts', ['*.tool.js'])).toBe(false);
      expect(matchesPattern('my-test.tool.js', ['*-test.tool.js'])).toBe(true);
      expect(matchesPattern('test.js', ['*.tool.js'])).toBe(false);
    });

    it('should match multiple patterns', () => {
      const matchesPattern = (ToolRegistry as unknown as ToolRegistryStatic).matchesPattern;

      expect(matchesPattern('test.tool.js', ['*.tool.js', '*.tool.ts'])).toBe(true);
      expect(matchesPattern('test.tool.ts', ['*.tool.js', '*.tool.ts'])).toBe(true);
      expect(matchesPattern('test.js', ['*.tool.js', '*.tool.ts'])).toBe(false);
    });

    it('should handle special regex characters in patterns', () => {
      const matchesPattern = (ToolRegistry as unknown as ToolRegistryStatic).matchesPattern;

      // Dots should be treated literally except after *
      expect(matchesPattern('test.tool.js', ['test.tool.js'])).toBe(true);
      expect(matchesPattern('testXtoolXjs', ['test.tool.js'])).toBe(false);

      // Brackets should be escaped
      expect(matchesPattern('[test].tool.js', ['[test].tool.js'])).toBe(true);
      expect(matchesPattern('t.tool.js', ['[test].tool.js'])).toBe(false);
    });
  });

  describe('Tool Registration', () => {
    it('should register a tool with proper validation', () => {
      const tool: ToolDefinition = {
        name: 'secure-tool',
        description: 'A secure tool',
        handler: vi.fn(),
        metadata: {
          category: 'security',
          tags: ['validated'],
        },
      };

      registry.registerTool(tool);

      expect(mockServer.registerTool).toHaveBeenCalledWith(
        'secure-tool',
        expect.objectContaining({
          description: 'A secure tool',
        }),
        expect.any(Function)
      );
    });

    it('should get tools by category with security filtering', () => {
      const tools: ToolDefinition[] = [
        {
          name: 'tool1',
          description: 'Tool 1',
          handler: vi.fn(),
          metadata: { category: 'secure' },
        },
        {
          name: 'tool2',
          description: 'Tool 2',
          handler: vi.fn(),
          metadata: { category: 'unsafe' },
        },
      ];

      tools.forEach((tool) => registry.registerTool(tool));

      const secureTooks = registry.getToolsByCategory('secure');
      expect(secureTooks).toEqual(['tool1']);
    });
  });
});
