import { beforeEach, describe, expect, it, mock } from 'bun:test';
import type { JobHandler, ScheduledJob } from '@stock-bot/types';
import { HandlerRegistry } from '../src/registry';
import type {
  HandlerConfiguration,
  HandlerMetadata,
  OperationMetadata,
  ScheduleMetadata,
} from '../src/types';

describe('HandlerRegistry Edge Cases', () => {
  let registry: HandlerRegistry;

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

  describe('Metadata Edge Cases', () => {
    it('should handle metadata without service', () => {
      const metadata: HandlerMetadata = {
        name: 'NoServiceHandler',
        operations: [],
      };

      registry.registerMetadata(metadata);

      expect(registry.getMetadata('NoServiceHandler')).toEqual(metadata);
      expect(registry.getHandlerService('NoServiceHandler')).toBeUndefined();
    });

    it('should handle metadata with optional fields', () => {
      const metadata: HandlerMetadata = {
        name: 'FullHandler',
        service: 'test-service',
        operations: [
          {
            name: 'op1',
            method: 'method1',
            description: 'Operation 1',
          },
        ],
        schedules: [
          {
            operation: 'op1',
            cronPattern: '*/5 * * * *',
            priority: 10,
            immediately: true,
            description: 'Every 5 minutes',
          },
        ],
        version: '1.0.0',
        description: 'Full handler with all fields',
      };

      registry.registerMetadata(metadata);

      const retrieved = registry.getMetadata('FullHandler');
      expect(retrieved).toEqual(metadata);
      expect(retrieved?.version).toBe('1.0.0');
      expect(retrieved?.description).toBe('Full handler with all fields');
      expect(retrieved?.schedules?.[0].immediately).toBe(true);
    });

    it('should handle empty operations array', () => {
      const metadata: HandlerMetadata = {
        name: 'EmptyHandler',
        operations: [],
      };

      registry.registerMetadata(metadata);

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

  describe('Configuration Edge Cases', () => {
    it('should handle configuration without scheduled jobs', () => {
      const config: HandlerConfiguration = {
        name: 'SimpleHandler',
        operations: {
          process: mock(async () => {}) as JobHandler,
        },
      };

      registry.registerConfiguration(config);

      const scheduledJobs = registry.getScheduledJobs('SimpleHandler');
      expect(scheduledJobs).toEqual([]);
    });

    it('should handle empty operations object', () => {
      const config: HandlerConfiguration = {
        name: 'EmptyOpsHandler',
        operations: {},
      };

      registry.registerConfiguration(config);

      expect(registry.getOperation('EmptyOpsHandler', 'nonexistent')).toBeUndefined();
    });

    it('should handle configuration with empty scheduled jobs array', () => {
      const config: HandlerConfiguration = {
        name: 'NoScheduleHandler',
        operations: {},
        scheduledJobs: [],
      };

      registry.registerConfiguration(config);

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

  describe('Service Management Edge Cases', () => {
    it('should update metadata when setting handler service', () => {
      const metadata: HandlerMetadata = {
        name: 'UpdateableHandler',
        operations: [],
        service: 'old-service',
      };

      registry.registerMetadata(metadata);
      registry.setHandlerService('UpdateableHandler', 'new-service');

      const updated = registry.getMetadata('UpdateableHandler');
      expect(updated?.service).toBe('new-service');
      expect(registry.getHandlerService('UpdateableHandler')).toBe('new-service');
    });

    it('should set service for non-existent handler', () => {
      registry.setHandlerService('NonExistentHandler', 'some-service');

      expect(registry.getHandlerService('NonExistentHandler')).toBe('some-service');
      expect(registry.getMetadata('NonExistentHandler')).toBeUndefined();
    });

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

    it('should handle multiple handlers for same service', () => {
      const metadata1: HandlerMetadata = {
        name: 'Handler1',
        service: 'shared-service',
        operations: [],
      };
      const metadata2: HandlerMetadata = {
        name: 'Handler2',
        service: 'shared-service',
        operations: [],
      };
      const metadata3: HandlerMetadata = {
        name: 'Handler3',
        service: 'other-service',
        operations: [],
      };

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

      const sharedHandlers = registry.getServiceHandlers('shared-service');
      expect(sharedHandlers).toHaveLength(2);
      expect(sharedHandlers.map(h => h.name).sort()).toEqual(['Handler1', 'Handler2']);
    });
  });

  describe('Operation Access Edge Cases', () => {
    it('should return undefined for non-existent handler operation', () => {
      const op = registry.getOperation('NonExistent', 'operation');
      expect(op).toBeUndefined();
    });

    it('should return undefined for non-existent operation name', () => {
      const config: HandlerConfiguration = {
        name: 'TestHandler',
        operations: {
          exists: mock(async () => {}) as JobHandler,
        },
      };

      registry.registerConfiguration(config);

      const op = registry.getOperation('TestHandler', 'notexists');
      expect(op).toBeUndefined();
    });
  });

  describe('getAllHandlersWithSchedule Edge Cases', () => {
    it('should handle mix of handlers with and without schedules', () => {
      const metadata1: HandlerMetadata = {
        name: 'WithSchedule',
        operations: [],
      };
      const config1: HandlerConfiguration = {
        name: 'WithSchedule',
        operations: {},
        scheduledJobs: [
          {
            name: 'job1',
            handler: mock(async () => {}) as JobHandler,
            pattern: '* * * * *',
          } as ScheduledJob,
        ],
      };

      const metadata2: HandlerMetadata = {
        name: 'WithoutSchedule',
        operations: [],
      };
      const config2: HandlerConfiguration = {
        name: 'WithoutSchedule',
        operations: {},
      };

      registry.register(metadata1, config1);
      registry.register(metadata2, config2);

      const allWithSchedule = registry.getAllHandlersWithSchedule();
      expect(allWithSchedule.size).toBe(2);

      const withSchedule = allWithSchedule.get('WithSchedule');
      expect(withSchedule?.scheduledJobs).toHaveLength(1);

      const withoutSchedule = allWithSchedule.get('WithoutSchedule');
      expect(withoutSchedule?.scheduledJobs).toEqual([]);
    });

    it('should handle handler with metadata but no configuration', () => {
      const metadata: HandlerMetadata = {
        name: 'MetadataOnly',
        operations: [],
      };

      registry.registerMetadata(metadata);

      const allWithSchedule = registry.getAllHandlersWithSchedule();
      const handler = allWithSchedule.get('MetadataOnly');

      expect(handler?.metadata).toEqual(metadata);
      expect(handler?.scheduledJobs).toEqual([]);
    });
  });

  describe('Import/Export Edge Cases', () => {
    it('should handle empty export', () => {
      const exported = registry.export();

      expect(exported.handlers).toEqual([]);
      expect(exported.configurations).toEqual([]);
      expect(exported.services).toEqual([]);
    });

    it('should handle empty import', () => {
      // Add some data first
      registry.registerMetadata({
        name: 'ExistingHandler',
        operations: [],
      });

      // Import empty data
      registry.import({
        handlers: [],
        configurations: [],
        services: [],
      });

      expect(registry.getHandlerNames()).toEqual([]);
    });

    it('should preserve complex data through export/import cycle', () => {
      const metadata: HandlerMetadata = {
        name: 'ComplexHandler',
        service: 'complex-service',
        operations: [
          { name: 'op1', method: 'method1' },
          { name: 'op2', method: 'method2' },
        ],
        schedules: [
          {
            operation: 'op1',
            cronPattern: '0 * * * *',
          },
        ],
      };

      const handler = mock(async () => {}) as JobHandler;
      const config: HandlerConfiguration = {
        name: 'ComplexHandler',
        operations: {
          op1: handler,
          op2: handler,
        },
        scheduledJobs: [
          {
            name: 'scheduled1',
            handler,
            pattern: '0 * * * *',
          } as ScheduledJob,
        ],
      };

      registry.register(metadata, config);
      registry.setHandlerService('ComplexHandler', 'overridden-service');

      const exported = registry.export();

      // Create new registry and import
      const newRegistry = new HandlerRegistry();
      newRegistry.import(exported);

      expect(newRegistry.getMetadata('ComplexHandler')).toEqual(metadata);
      expect(newRegistry.getConfiguration('ComplexHandler')).toEqual(config);
      expect(newRegistry.getHandlerService('ComplexHandler')).toBe('overridden-service');
    });
  });

  describe('Statistics Edge Cases', () => {
    it('should count schedules from metadata', () => {
      const metadata: HandlerMetadata = {
        name: 'ScheduledHandler',
        operations: [{ name: 'op1', method: 'method1' }],
        schedules: [
          { operation: 'op1', cronPattern: '* * * * *' },
          { operation: 'op1', cronPattern: '0 * * * *' },
        ],
      };

      registry.registerMetadata(metadata);

      const stats = registry.getStats();
      expect(stats.handlers).toBe(1);
      expect(stats.operations).toBe(1);
      expect(stats.scheduledJobs).toBe(2);
      expect(stats.services).toBe(0); // No service specified
    });

    it('should not double count services', () => {
      registry.registerMetadata({
        name: 'Handler1',
        service: 'service1',
        operations: [],
      });

      registry.registerMetadata({
        name: 'Handler2',
        service: 'service1', // Same service
        operations: [],
      });

      registry.registerMetadata({
        name: 'Handler3',
        service: 'service2',
        operations: [],
      });

      const stats = registry.getStats();
      expect(stats.services).toBe(2); // Only 2 unique services
    });
  });

  describe('Error Scenarios', () => {
    it('should handle undefined values gracefully', () => {
      expect(registry.getMetadata(undefined as any)).toBeUndefined();
      expect(registry.getConfiguration(undefined as any)).toBeUndefined();
      expect(registry.getOperation(undefined as any, 'op')).toBeUndefined();
      expect(registry.hasHandler(undefined as any)).toBe(false);
    });

    it('should handle null service lookup', () => {
      const handlers = registry.getServiceHandlers(null as any);
      expect(handlers).toEqual([]);
    });
  });
});
