import { beforeEach, describe, expect, it } from 'bun:test';
import {
  Disabled,
  Handler,
  Operation,
  QueueSchedule,
  ScheduledOperation,
} from '../src/decorators/decorators';

describe('Handler Decorators', () => {
  beforeEach(() => {
    // Clear metadata between tests
    (global as any).__handlerMetadata = undefined;
  });

  describe('@Handler', () => {
    it('should mark class as handler with name', () => {
      @Handler('TestHandler')
      class MyHandler {}

      const constructor = MyHandler as any;

      expect(constructor.__handlerName).toBe('TestHandler');
      expect(constructor.__needsAutoRegistration).toBe(true);
    });

    it('should use class name if no name provided', () => {
      // Handler decorator requires a name parameter
      @Handler('MyTestHandler')
      class MyTestHandler {}

      const constructor = MyTestHandler as any;

      expect(constructor.__handlerName).toBe('MyTestHandler');
    });

    it('should work with inheritance', () => {
      @Handler('BaseHandler')
      class BaseTestHandler {}

      @Handler('DerivedHandler')
      class DerivedTestHandler extends BaseTestHandler {}

      const baseConstructor = BaseTestHandler as any;
      const derivedConstructor = DerivedTestHandler as any;

      expect(baseConstructor.__handlerName).toBe('BaseHandler');
      expect(derivedConstructor.__handlerName).toBe('DerivedHandler');
    });
  });

  describe('@Operation', () => {
    it('should mark method as operation', () => {
      class TestHandler {
        @Operation('processData')
        async process(data: unknown) {
          return data;
        }
      }

      const constructor = TestHandler as any;

      expect(constructor.__operations).toBeDefined();
      expect(constructor.__operations).toHaveLength(1);
      expect(constructor.__operations[0]).toEqual({
        name: 'processData',
        method: 'process',
        batch: undefined,
      });
    });

    it('should use method name if no name provided', () => {
      // Operation decorator requires a name parameter
      class TestHandler {
        @Operation('processOrder')
        async processOrder(data: unknown) {
          return data;
        }
      }

      const constructor = TestHandler as any;

      expect(constructor.__operations).toBeDefined();
      expect(constructor.__operations[0]).toEqual({
        name: 'processOrder',
        method: 'processOrder',
        batch: undefined,
      });
    });

    it('should support batch configuration', () => {
      class TestHandler {
        @Operation('batchProcess', { batch: { enabled: true, size: 10, delayInHours: 1 } })
        async processBatch(items: unknown[]) {
          return items;
        }
      }

      const constructor = TestHandler as any;

      expect(constructor.__operations).toBeDefined();
      expect(constructor.__operations[0]).toEqual({
        name: 'batchProcess',
        method: 'processBatch',
        batch: { enabled: true, size: 10, delayInHours: 1 },
      });
    });

    it('should work with multiple operations', () => {
      class TestHandler {
        @Operation('op1')
        async operation1() {}

        @Operation('op2')
        async operation2() {}

        @Operation('op3')
        async operation3() {}
      }

      const constructor = TestHandler as any;

      expect(constructor.__operations).toHaveLength(3);
      expect(constructor.__operations[0]).toMatchObject({ name: 'op1', method: 'operation1' });
      expect(constructor.__operations[1]).toMatchObject({ name: 'op2', method: 'operation2' });
      expect(constructor.__operations[2]).toMatchObject({ name: 'op3', method: 'operation3' });
    });
  });

  describe('@Disabled', () => {
    it('should mark handler as disabled', () => {
      @Disabled()
      @Handler('DisabledHandler')
      class MyDisabledHandler {}

      const constructor = MyDisabledHandler as any;

      expect(constructor.__handlerName).toBe('DisabledHandler');
      expect(constructor.__disabled).toBe(true);
    });

    it('should work when applied after Handler decorator', () => {
      @Handler('TestHandler')
      @Disabled()
      class MyHandler {}

      const constructor = MyHandler as any;

      expect(constructor.__handlerName).toBe('TestHandler');
      expect(constructor.__disabled).toBe(true);
    });
  });

  describe('@QueueSchedule', () => {
    it('should add queue schedule to operation', () => {
      class TestHandler {
        @QueueSchedule('0 0 * * *')
        @Operation('dailyTask')
        async runDaily() {}
      }

      const constructor = TestHandler as any;

      expect(constructor.__schedules).toBeDefined();
      expect(constructor.__schedules[0]).toMatchObject({
        operation: 'runDaily',
        cronPattern: '0 0 * * *',
      });
    });

    it('should work with multiple scheduled operations', () => {
      class TestHandler {
        @QueueSchedule('0 * * * *')
        @Operation('hourlyTask')
        async runHourly() {}

        @QueueSchedule('0 0 * * *')
        @Operation('dailyTask')
        async runDaily() {}
      }

      const constructor = TestHandler as any;

      expect(constructor.__schedules).toBeDefined();
      expect(constructor.__schedules).toHaveLength(2);
      expect(constructor.__schedules[0]).toMatchObject({
        operation: 'runHourly',
        cronPattern: '0 * * * *',
      });
      expect(constructor.__schedules[1]).toMatchObject({
        operation: 'runDaily',
        cronPattern: '0 0 * * *',
      });
    });
  });

  describe('@ScheduledOperation', () => {
    it('should mark operation as scheduled with options', () => {
      class TestHandler {
        @ScheduledOperation('syncData', '*/5 * * * *', {
          priority: 10,
          immediately: true,
          description: 'Sync data every 5 minutes',
        })
        async syncOperation() {}
      }

      const constructor = TestHandler as any;

      // ScheduledOperation creates both an operation and a schedule
      expect(constructor.__operations).toBeDefined();
      expect(constructor.__operations[0]).toMatchObject({
        name: 'syncData',
        method: 'syncOperation',
      });

      expect(constructor.__schedules).toBeDefined();
      expect(constructor.__schedules[0]).toMatchObject({
        operation: 'syncOperation',
        cronPattern: '*/5 * * * *',
        priority: 10,
        immediately: true,
        description: 'Sync data every 5 minutes',
      });
    });

    it('should use method name if not provided', () => {
      class TestHandler {
        @ScheduledOperation('dailyCleanup', '0 0 * * *')
        async dailyCleanup() {}
      }

      const constructor = TestHandler as any;

      expect(constructor.__operations[0]).toMatchObject({
        name: 'dailyCleanup',
        method: 'dailyCleanup',
      });
      expect(constructor.__schedules[0]).toMatchObject({
        operation: 'dailyCleanup',
        cronPattern: '0 0 * * *',
      });
    });

    it('should handle multiple scheduled operations', () => {
      class TestHandler {
        @ScheduledOperation('hourlyCheck', '0 * * * *')
        async hourlyCheck() {}

        @ScheduledOperation('dailyReport', '0 0 * * *')
        async dailyReport() {}

        @ScheduledOperation('weeklyAnalysis', '0 0 * * 0')
        async weeklyAnalysis() {}
      }

      const constructor = TestHandler as any;

      expect(constructor.__operations).toHaveLength(3);
      expect(constructor.__schedules).toHaveLength(3);

      expect(constructor.__operations[0]).toMatchObject({ name: 'hourlyCheck' });
      expect(constructor.__operations[1]).toMatchObject({ name: 'dailyReport' });
      expect(constructor.__operations[2]).toMatchObject({ name: 'weeklyAnalysis' });

      expect(constructor.__schedules[0]).toMatchObject({ cronPattern: '0 * * * *' });
      expect(constructor.__schedules[1]).toMatchObject({ cronPattern: '0 0 * * *' });
      expect(constructor.__schedules[2]).toMatchObject({ cronPattern: '0 0 * * 0' });
    });
  });

  describe('decorator composition', () => {
    it('should work with all decorators combined', () => {
      @Handler('ComplexHandler')
      class MyComplexHandler {
        @Operation('complexOp', { batch: { enabled: true, size: 5 } })
        @QueueSchedule('0 */6 * * *')
        async complexOperation(items: unknown[]) {
          return items;
        }

        @ScheduledOperation('scheduledTask', '0 0 * * *', {
          priority: 5,
          description: 'Daily scheduled task',
        })
        async scheduledTask() {}
      }

      const constructor = MyComplexHandler as any;

      expect(constructor.__handlerName).toBe('ComplexHandler');

      // Check operations
      expect(constructor.__operations).toHaveLength(2);
      expect(constructor.__operations[0]).toMatchObject({
        name: 'complexOp',
        method: 'complexOperation',
        batch: { enabled: true, size: 5 },
      });
      expect(constructor.__operations[1]).toMatchObject({
        name: 'scheduledTask',
        method: 'scheduledTask',
      });

      // Check schedules
      expect(constructor.__schedules).toHaveLength(2);
      expect(constructor.__schedules[0]).toMatchObject({
        operation: 'complexOperation',
        cronPattern: '0 */6 * * *',
      });
      expect(constructor.__schedules[1]).toMatchObject({
        operation: 'scheduledTask',
        cronPattern: '0 0 * * *',
        priority: 5,
        description: 'Daily scheduled task',
      });
    });
  });
});
