import { beforeEach, describe, expect, it } from 'bun:test';
import { HandlerRegistry } from '../src/registry';
import type {
  HandlerConfiguration,
  HandlerMetadata,
  OperationMetadata,
  ScheduleMetadata,
} from '../src/types';

describe('HandlerRegistry Comprehensive Tests', () => {
  let registry: HandlerRegistry;

  beforeEach(() => {
    registry = new HandlerRegistry();
  });

  describe('registerMetadata', () => {
    it('should register handler metadata separately', () => {
      const metadata: HandlerMetadata = {
        name: 'TestHandler',
        service: 'test-service',
        operations: [
          {
            name: 'processData',
            method: 'processData',
          },
          {
            name: 'batchProcess',
            method: 'batchProcess',
          },
        ],
      };

      registry.registerMetadata(metadata);

      const retrieved = registry.getMetadata('TestHandler');
      expect(retrieved).toEqual(metadata);
    });

    it('should overwrite existing metadata', () => {
      const metadata1: HandlerMetadata = {
        name: 'TestHandler',
        service: 'service1',
        operations: [{ name: 'op1', method: 'op1' }],
      };

      const metadata2: HandlerMetadata = {
        name: 'TestHandler',
        service: 'service2',
        operations: [{ name: 'op2', method: 'op2' }],
      };

      registry.registerMetadata(metadata1);
      registry.registerMetadata(metadata2);

      const retrieved = registry.getMetadata('TestHandler');
      expect(retrieved).toEqual(metadata2);
    });
  });

  describe('registerConfiguration', () => {
    it('should register handler configuration separately', () => {
      const config: HandlerConfiguration = {
        name: 'TestHandler',
        operations: {
          processData: async (data: unknown) => ({ processed: data }),
          batchProcess: async (items: unknown[]) => items.map(i => ({ processed: i })),
        },
      };

      registry.registerConfiguration(config);

      const retrieved = registry.getConfiguration('TestHandler');
      expect(retrieved).toEqual(config);
    });

    it('should handle async operations', async () => {
      const config: HandlerConfiguration = {
        name: 'AsyncHandler',
        operations: {
          asyncOp: async (data: unknown) => {
            await new Promise(resolve => setTimeout(resolve, 10));
            return { result: data };
          },
        },
      };

      registry.registerConfiguration(config);

      const operation = registry.getOperation('AsyncHandler', 'asyncOp');
      expect(operation).toBeDefined();

      const result = await operation!({ value: 42 });
      expect(result).toEqual({ result: { value: 42 } });
    });
  });

  describe('getMetadata', () => {
    it('should return handler metadata', () => {
      const metadata: HandlerMetadata = {
        name: 'MetaHandler',
        service: 'meta-service',
        operations: [{ name: 'metaOp', method: 'metaOp' }],
      };

      registry.registerMetadata(metadata);

      const retrieved = registry.getMetadata('MetaHandler');
      expect(retrieved).toEqual(metadata);
    });

    it('should return undefined for non-existent handler', () => {
      const metadata = registry.getMetadata('NonExistent');
      expect(metadata).toBeUndefined();
    });
  });

  describe('getServiceHandlers', () => {
    it('should return handlers for a specific service', () => {
      const metadata1: HandlerMetadata = {
        name: 'Handler1',
        service: 'service-a',
        operations: [],
      };
      const config1: HandlerConfiguration = {
        name: 'Handler1',
        operations: {},
      };
      registry.register(metadata1, config1);

      const metadata2: HandlerMetadata = {
        name: 'Handler2',
        service: 'service-a',
        operations: [],
      };
      const config2: HandlerConfiguration = {
        name: 'Handler2',
        operations: {},
      };
      registry.register(metadata2, config2);

      const metadata3: HandlerMetadata = {
        name: 'Handler3',
        service: 'service-b',
        operations: [],
      };
      const config3: HandlerConfiguration = {
        name: 'Handler3',
        operations: {},
      };
      registry.register(metadata3, config3);

      const serviceAHandlers = registry.getServiceHandlers('service-a');
      expect(serviceAHandlers).toHaveLength(2);
      expect(serviceAHandlers.map(h => h.name)).toContain('Handler1');
      expect(serviceAHandlers.map(h => h.name)).toContain('Handler2');

      const serviceBHandlers = registry.getServiceHandlers('service-b');
      expect(serviceBHandlers).toHaveLength(1);
      expect(serviceBHandlers[0].name).toBe('Handler3');
    });

    it('should return empty array for non-existent service', () => {
      const handlers = registry.getServiceHandlers('non-existent-service');
      expect(handlers).toEqual([]);
    });
  });

  describe('setHandlerService and getHandlerService', () => {
    it('should set and get handler service ownership', () => {
      const metadata: HandlerMetadata = {
        name: 'ServiceHandler',
        operations: [],
      };
      const config: HandlerConfiguration = {
        name: 'ServiceHandler',
        operations: {},
      };
      registry.register(metadata, config);

      registry.setHandlerService('ServiceHandler', 'my-service');

      const service = registry.getHandlerService('ServiceHandler');
      expect(service).toBe('my-service');
    });

    it('should overwrite existing service ownership', () => {
      const metadata: HandlerMetadata = {
        name: 'ServiceHandler',
        service: 'initial-service',
        operations: [],
      };
      const config: HandlerConfiguration = {
        name: 'ServiceHandler',
        operations: {},
      };
      registry.register(metadata, config);

      registry.setHandlerService('ServiceHandler', 'new-service');

      const service = registry.getHandlerService('ServiceHandler');
      expect(service).toBe('new-service');
    });

    it('should return undefined for non-existent handler', () => {
      const service = registry.getHandlerService('NonExistent');
      expect(service).toBeUndefined();
    });
  });

  describe('getScheduledJobs', () => {
    it('should return scheduled jobs for a handler', () => {
      const schedules: ScheduleMetadata[] = [
        {
          operation: 'dailyJob',
          cronPattern: '0 0 * * *',
          priority: 1,
        },
        {
          operation: 'hourlyJob',
          cronPattern: '0 * * * *',
        },
      ];

      const metadata: HandlerMetadata = {
        name: 'ScheduledHandler',
        operations: [
          { name: 'dailyJob', method: 'dailyJob' },
          { name: 'hourlyJob', method: 'hourlyJob' },
        ],
        schedules,
      };
      const config: HandlerConfiguration = {
        name: 'ScheduledHandler',
        operations: {
          dailyJob: async () => ({ result: 'daily' }),
          hourlyJob: async () => ({ result: 'hourly' }),
        },
        scheduledJobs: [
          {
            type: 'dailyJob',
            operation: 'dailyJob',
            cronPattern: '0 0 * * *',
            priority: 1,
          },
          {
            type: 'hourlyJob',
            operation: 'hourlyJob',
            cronPattern: '0 * * * *',
          },
        ],
      };
      registry.register(metadata, config);

      const jobs = registry.getScheduledJobs('ScheduledHandler');
      expect(jobs).toHaveLength(2);
      expect(jobs[0].type).toBe('dailyJob');
      expect(jobs[1].type).toBe('hourlyJob');
    });

    it('should return empty array for handler without schedules', () => {
      const metadata: HandlerMetadata = {
        name: 'NoScheduleHandler',
        operations: [],
      };
      const config: HandlerConfiguration = {
        name: 'NoScheduleHandler',
        operations: {},
      };
      registry.register(metadata, config);

      const jobs = registry.getScheduledJobs('NoScheduleHandler');
      expect(jobs).toEqual([]);
    });

    it('should return empty array for non-existent handler', () => {
      const jobs = registry.getScheduledJobs('NonExistent');
      expect(jobs).toEqual([]);
    });
  });

  describe('getStats', () => {
    it('should return registry statistics', () => {
      // Register handlers with various configurations
      const metadata1: HandlerMetadata = {
        name: 'Handler1',
        service: 'service-a',
        operations: [
          { name: 'op1', method: 'op1' },
          { name: 'op2', method: 'op2' },
        ],
        schedules: [{ operation: 'op1', cronPattern: '0 0 * * *' }],
      };
      const config1: HandlerConfiguration = {
        name: 'Handler1',
        operations: {
          op1: async () => ({}),
          op2: async () => ({}),
        },
      };
      registry.register(metadata1, config1);

      const metadata2: HandlerMetadata = {
        name: 'Handler2',
        service: 'service-b',
        operations: [{ name: 'op3', method: 'op3' }],
      };
      const config2: HandlerConfiguration = {
        name: 'Handler2',
        operations: {
          op3: async () => ({}),
        },
      };
      registry.register(metadata2, config2);

      const stats = registry.getStats();

      expect(stats.handlers).toBe(2);
      expect(stats.operations).toBe(3);
      expect(stats.scheduledJobs).toBe(1);
      expect(stats.services).toBe(2);
    });

    it('should return zero stats for empty registry', () => {
      const stats = registry.getStats();

      expect(stats.handlers).toBe(0);
      expect(stats.operations).toBe(0);
      expect(stats.scheduledJobs).toBe(0);
      expect(stats.services).toBe(0);
    });
  });

  describe('clear', () => {
    it('should clear all registrations', () => {
      const metadata1: HandlerMetadata = {
        name: 'Handler1',
        operations: [],
      };
      const config1: HandlerConfiguration = {
        name: 'Handler1',
        operations: {},
      };
      registry.register(metadata1, config1);

      const metadata2: HandlerMetadata = {
        name: 'Handler2',
        operations: [],
      };
      const config2: HandlerConfiguration = {
        name: 'Handler2',
        operations: {},
      };
      registry.register(metadata2, config2);

      expect(registry.getHandlerNames()).toHaveLength(2);

      registry.clear();

      expect(registry.getHandlerNames()).toHaveLength(0);
      expect(registry.getAllMetadata().size).toBe(0);
      expect(registry.getStats().handlers).toBe(0);
    });
  });

  describe('export and import', () => {
    it('should export and import registry data', () => {
      // Setup initial registry
      const metadata1: HandlerMetadata = {
        name: 'ExportHandler1',
        service: 'export-service',
        operations: [{ name: 'exportOp', method: 'exportOp' }],
        schedules: [{ operation: 'exportOp', cronPattern: '0 0 * * *' }],
      };
      const config1: HandlerConfiguration = {
        name: 'ExportHandler1',
        operations: {
          exportOp: async () => ({ exported: true }),
        },
      };
      registry.register(metadata1, config1);

      const metadata2: HandlerMetadata = {
        name: 'ExportHandler2',
        operations: [{ name: 'anotherOp', method: 'anotherOp' }],
      };
      const config2: HandlerConfiguration = {
        name: 'ExportHandler2',
        operations: {
          anotherOp: async () => ({ another: true }),
        },
      };
      registry.register(metadata2, config2);

      // Export data
      const exportedData = registry.export();

      expect(exportedData.handlers).toHaveLength(2);
      expect(exportedData.configurations).toHaveLength(2);
      expect(exportedData.services).toHaveLength(1); // Only ExportHandler1 has a service

      // Clear and verify empty
      registry.clear();
      expect(registry.getHandlerNames()).toHaveLength(0);

      // Import data
      registry.import(exportedData);

      // Verify restored
      expect(registry.getHandlerNames()).toHaveLength(2);
      expect(registry.hasHandler('ExportHandler1')).toBe(true);
      expect(registry.hasHandler('ExportHandler2')).toBe(true);

      const handler1 = registry.getMetadata('ExportHandler1');
      expect(handler1?.service).toBe('export-service');
      expect(handler1?.schedules).toHaveLength(1);

      const handler2 = registry.getMetadata('ExportHandler2');
      expect(handler2?.operations).toHaveLength(1);
      expect(handler2?.operations[0].name).toBe('anotherOp');
    });

    it('should handle import with empty data', () => {
      const emptyData = {
        handlers: [],
        configurations: [],
        services: [],
      };

      registry.import(emptyData);

      expect(registry.getHandlerNames()).toHaveLength(0);
    });

    it('should preserve configurations during export/import', async () => {
      const testData = { value: 42 };

      const metadata: HandlerMetadata = {
        name: 'ConfigHandler',
        operations: [{ name: 'configOp', method: 'configOp' }],
      };
      const config: HandlerConfiguration = {
        name: 'ConfigHandler',
        operations: {
          configOp: async (data: any) => ({ processed: data.value * 2 }),
        },
      };
      registry.register(metadata, config);

      // Test operation before export
      const opBefore = registry.getOperation('ConfigHandler', 'configOp');
      const resultBefore = await opBefore!(testData);
      expect(resultBefore).toEqual({ processed: 84 });

      // Export and import
      const exported = registry.export();
      registry.clear();
      registry.import(exported);

      // Test operation after import - configurations are preserved
      const opAfter = registry.getOperation('ConfigHandler', 'configOp');
      expect(opAfter).toBeDefined();
      const resultAfter = await opAfter!(testData);
      expect(resultAfter).toEqual({ processed: 84 });
    });
  });

  describe('edge cases', () => {
    it('should handle empty operations object', () => {
      const metadata: HandlerMetadata = {
        name: 'EmptyHandler',
        operations: [],
      };
      const config: HandlerConfiguration = {
        name: 'EmptyHandler',
        operations: {},
      };
      registry.register(metadata, config);

      const retrieved = registry.getMetadata('EmptyHandler');
      expect(retrieved?.operations).toEqual([]);

      const stats = registry.getStats();
      expect(stats.operations).toBe(0);
    });

    it('should handle handlers with many operations', () => {
      const operations: OperationMetadata[] = [];
      const operationHandlers: Record<string, JobHandler> = {};

      // Create 50 operations
      for (let i = 0; i < 50; i++) {
        const opName = `operation${i}`;
        operations.push({
          name: opName,
          method: opName,
        });
        operationHandlers[opName] = (async () => ({ index: i })) as JobHandler;
      }

      const metadata: HandlerMetadata = {
        name: 'ManyOpsHandler',
        operations,
      };
      const config: HandlerConfiguration = {
        name: 'ManyOpsHandler',
        operations: operationHandlers,
      };
      registry.register(metadata, config);

      const retrieved = registry.getMetadata('ManyOpsHandler');
      expect(retrieved!.operations).toHaveLength(50);

      const stats = registry.getStats();
      expect(stats.operations).toBe(50);
    });

    it('should handle concurrent registrations', async () => {
      const promises = [];

      // Register 10 handlers concurrently
      for (let i = 0; i < 10; i++) {
        promises.push(
          Promise.resolve().then(() => {
            const metadata: HandlerMetadata = {
              name: `ConcurrentHandler${i}`,
              operations: [{ name: 'op', method: 'op' }],
            };
            const config: HandlerConfiguration = {
              name: `ConcurrentHandler${i}`,
              operations: {
                op: async () => ({ handler: i }),
              },
            };
            registry.register(metadata, config);
          })
        );
      }

      await Promise.all(promises);

      expect(registry.getHandlerNames()).toHaveLength(10);

      // Verify all handlers registered correctly
      for (let i = 0; i < 10; i++) {
        expect(registry.hasHandler(`ConcurrentHandler${i}`)).toBe(true);
      }
    });
  });
});
