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

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

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

  describe('register', () => {
    it('should register a handler', () => {
      const metadata: HandlerMetadata = {
        name: 'test-handler',
        service: 'test-service',
        operations: [
          { name: 'operation1', method: 'method1' },
          { name: 'operation2', method: 'method2' },
        ],
      };

      const config: HandlerConfiguration = {
        name: 'test-handler',
        operations: {
          operation1: async () => {},
          operation2: async () => {},
        },
      };

      registry.register(metadata, config);

      expect(registry.hasHandler('test-handler')).toBe(true);
      expect(registry.getHandlerNames()).toContain('test-handler');
    });

    it('should allow duplicate registration', () => {
      const metadata: HandlerMetadata = {
        name: 'duplicate',
        operations: [],
      };

      const config: HandlerConfiguration = {
        name: 'duplicate',
        operations: {},
      };

      registry.register(metadata, config);

      // Should not throw on duplicate registration
      expect(() => {
        registry.register(metadata, config);
      }).not.toThrow();
    });
  });

  describe('getConfiguration', () => {
    it('should return registered handler config', () => {
      const config: HandlerConfiguration = {
        name: 'my-handler',
        operations: {
          doSomething: async () => 'result',
        },
      };

      registry.register({ name: 'my-handler', operations: [] }, config);

      const retrieved = registry.getConfiguration('my-handler');
      expect(retrieved).toBe(config);
    });

    it('should return undefined for unknown handler', () => {
      const result = registry.getConfiguration('unknown');
      expect(result).toBeUndefined();
    });
  });

  describe('getOperation', () => {
    it('should return operation handler', () => {
      const operationFn = async () => 'test';

      registry.register(
        { name: 'handler1', operations: [{ name: 'op1', method: 'method1' }] },
        { name: 'handler1', operations: { op1: operationFn } }
      );

      const retrieved = registry.getOperation('handler1', 'op1');
      expect(retrieved).toBe(operationFn);
    });

    it('should return undefined for unknown operation', () => {
      registry.register({ name: 'handler1', operations: [] }, { name: 'handler1', operations: {} });

      const result = registry.getOperation('handler1', 'unknown');
      expect(result).toBeUndefined();
    });
  });

  describe('scheduled jobs', () => {
    it('should register handler with scheduled jobs', () => {
      const metadata: HandlerMetadata = {
        name: 'scheduled-handler',
        operations: [],
        schedules: [
          {
            operation: 'scheduled-op',
            cronPattern: '* * * * *',
            priority: 5,
          },
        ],
      };

      const config: HandlerConfiguration = {
        name: 'scheduled-handler',
        operations: {},
        scheduledJobs: [
          {
            type: 'scheduled-handler-scheduled-op',
            operation: 'scheduled-op',
            cronPattern: '* * * * *',
            priority: 5,
          },
        ],
      };

      registry.register(metadata, config);

      const handlers = registry.getAllHandlersWithSchedule();
      expect(handlers.size).toBe(1);
      expect(handlers.has('scheduled-handler')).toBe(true);

      const handlerData = handlers.get('scheduled-handler');
      expect(handlerData?.scheduledJobs).toHaveLength(1);
    });

    it('should return all handlers including those without schedules', () => {
      registry.register(
        { name: 'no-schedule', operations: [] },
        { name: 'no-schedule', operations: {} }
      );

      registry.register(
        {
          name: 'with-schedule',
          operations: [],
          schedules: [{ operation: 'op', cronPattern: '* * * * *' }],
        },
        {
          name: 'with-schedule',
          operations: {},
          scheduledJobs: [{ type: 'job', operation: 'op', cronPattern: '* * * * *' }],
        }
      );

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

      const noScheduleData = handlers.get('no-schedule');
      expect(noScheduleData?.scheduledJobs).toHaveLength(0);

      const withScheduleData = handlers.get('with-schedule');
      expect(withScheduleData?.scheduledJobs).toHaveLength(1);
    });
  });

  describe('service mapping', () => {
    it('should track handler service', () => {
      registry.register(
        { name: 'handler1', service: 'service-a', operations: [] },
        { name: 'handler1', operations: {} }
      );

      expect(registry.getHandlerService('handler1')).toBe('service-a');
    });

    it('should return undefined for handler without service', () => {
      registry.register({ name: 'handler2', operations: [] }, { name: 'handler2', operations: {} });

      expect(registry.getHandlerService('handler2')).toBeUndefined();
    });
  });

  describe('getAllMetadata', () => {
    it('should return all registered handlers', () => {
      registry.register({ name: 'h1', operations: [] }, { name: 'h1', operations: {} });

      registry.register({ name: 'h2', operations: [] }, { name: 'h2', operations: {} });

      const all = registry.getAllMetadata();
      expect(all.size).toBe(2);
      expect(all.has('h1')).toBe(true);
      expect(all.has('h2')).toBe(true);
    });
  });
});
