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

describe('Decorators Edge Cases', () => {
  describe('Handler Decorator', () => {
    it('should add metadata to class constructor', () => {
      @Handler('test-handler')
      class TestHandler extends BaseHandler {}

      const ctor = TestHandler as any;
      expect(ctor.__handlerName).toBe('test-handler');
      expect(ctor.__needsAutoRegistration).toBe(true);
    });

    it('should handle empty handler name', () => {
      @Handler('')
      class EmptyNameHandler extends BaseHandler {}

      const ctor = EmptyNameHandler as any;
      expect(ctor.__handlerName).toBe('');
    });

    it('should work with context parameter', () => {
      const HandlerClass = Handler('with-context')(class TestClass extends BaseHandler {}, {
        kind: 'class',
      });

      const ctor = HandlerClass as any;
      expect(ctor.__handlerName).toBe('with-context');
    });
  });

  describe('Operation Decorator', () => {
    it('should add operation metadata', () => {
      class TestHandler extends BaseHandler {
        @Operation('test-op')
        testMethod() {}
      }

      const ctor = TestHandler as any;
      expect(ctor.__operations).toBeDefined();
      expect(ctor.__operations).toHaveLength(1);
      expect(ctor.__operations[0]).toEqual({
        name: 'test-op',
        method: 'testMethod',
        batch: undefined,
      });
    });

    it('should handle multiple operations', () => {
      class TestHandler extends BaseHandler {
        @Operation('op1')
        method1() {}

        @Operation('op2')
        method2() {}
      }

      const ctor = TestHandler as any;
      expect(ctor.__operations).toHaveLength(2);
      expect(ctor.__operations.map((op: any) => op.name)).toEqual(['op1', 'op2']);
    });

    it('should handle batch configuration', () => {
      class TestHandler extends BaseHandler {
        @Operation('batch-op', {
          batch: {
            enabled: true,
            size: 100,
            delayInHours: 24,
            priority: 5,
            direct: false,
          },
        })
        batchMethod() {}
      }

      const ctor = TestHandler as any;
      expect(ctor.__operations[0].batch).toEqual({
        enabled: true,
        size: 100,
        delayInHours: 24,
        priority: 5,
        direct: false,
      });
    });

    it('should handle partial batch configuration', () => {
      class TestHandler extends BaseHandler {
        @Operation('partial-batch', {
          batch: {
            enabled: true,
            size: 50,
          },
        })
        partialBatchMethod() {}
      }

      const ctor = TestHandler as any;
      expect(ctor.__operations[0].batch).toEqual({
        enabled: true,
        size: 50,
      });
    });

    it('should handle empty operation name', () => {
      class TestHandler extends BaseHandler {
        @Operation('')
        emptyOp() {}
      }

      const ctor = TestHandler as any;
      expect(ctor.__operations[0].name).toBe('');
    });
  });

  describe('QueueSchedule Decorator', () => {
    it('should add schedule metadata', () => {
      class TestHandler extends BaseHandler {
        @QueueSchedule('* * * * *')
        scheduledMethod() {}
      }

      const ctor = TestHandler as any;
      expect(ctor.__schedules).toBeDefined();
      expect(ctor.__schedules).toHaveLength(1);
      expect(ctor.__schedules[0]).toEqual({
        operation: 'scheduledMethod',
        cronPattern: '* * * * *',
      });
    });

    it('should handle full options', () => {
      class TestHandler extends BaseHandler {
        @QueueSchedule('0 * * * *', {
          priority: 10,
          immediately: true,
          description: 'Hourly job',
          payload: { type: 'scheduled' },
          batch: {
            enabled: true,
            size: 200,
            delayInHours: 1,
            priority: 8,
            direct: true,
          },
        })
        hourlyJob() {}
      }

      const ctor = TestHandler as any;
      const schedule = ctor.__schedules[0];
      expect(schedule.priority).toBe(10);
      expect(schedule.immediately).toBe(true);
      expect(schedule.description).toBe('Hourly job');
      expect(schedule.payload).toEqual({ type: 'scheduled' });
      expect(schedule.batch).toEqual({
        enabled: true,
        size: 200,
        delayInHours: 1,
        priority: 8,
        direct: true,
      });
    });

    it('should handle invalid cron pattern', () => {
      // Decorator doesn't validate - it just stores the pattern
      class TestHandler extends BaseHandler {
        @QueueSchedule('invalid cron')
        invalidSchedule() {}
      }

      const ctor = TestHandler as any;
      expect(ctor.__schedules[0].cronPattern).toBe('invalid cron');
    });

    it('should handle multiple schedules', () => {
      class TestHandler extends BaseHandler {
        @QueueSchedule('*/5 * * * *')
        every5Minutes() {}

        @QueueSchedule('0 0 * * *')
        daily() {}
      }

      const ctor = TestHandler as any;
      expect(ctor.__schedules).toHaveLength(2);
      expect(ctor.__schedules.map((s: any) => s.operation)).toEqual(['every5Minutes', 'daily']);
    });
  });

  describe('ScheduledOperation Decorator', () => {
    it('should apply both Operation and QueueSchedule', () => {
      class TestHandler extends BaseHandler {
        @ScheduledOperation('combined-op', '*/10 * * * *')
        combinedMethod() {}
      }

      const ctor = TestHandler as any;

      // Check operation was added
      expect(ctor.__operations).toBeDefined();
      expect(ctor.__operations).toHaveLength(1);
      expect(ctor.__operations[0].name).toBe('combined-op');

      // Check schedule was added
      expect(ctor.__schedules).toBeDefined();
      expect(ctor.__schedules).toHaveLength(1);
      expect(ctor.__schedules[0].cronPattern).toBe('*/10 * * * *');
    });

    it('should pass batch config to both decorators', () => {
      class TestHandler extends BaseHandler {
        @ScheduledOperation('batch-scheduled', '0 */6 * * *', {
          priority: 7,
          immediately: false,
          description: 'Every 6 hours',
          payload: { scheduled: true },
          batch: {
            enabled: true,
            size: 500,
            delayInHours: 6,
          },
        })
        batchScheduledMethod() {}
      }

      const ctor = TestHandler as any;

      // Check operation has batch config
      expect(ctor.__operations[0].batch).toEqual({
        enabled: true,
        size: 500,
        delayInHours: 6,
      });

      // Check schedule has all options
      const schedule = ctor.__schedules[0];
      expect(schedule.priority).toBe(7);
      expect(schedule.immediately).toBe(false);
      expect(schedule.description).toBe('Every 6 hours');
      expect(schedule.payload).toEqual({ scheduled: true });
      expect(schedule.batch).toEqual({
        enabled: true,
        size: 500,
        delayInHours: 6,
      });
    });

    it('should handle minimal configuration', () => {
      class TestHandler extends BaseHandler {
        @ScheduledOperation('minimal', '* * * * *')
        minimalMethod() {}
      }

      const ctor = TestHandler as any;
      expect(ctor.__operations[0]).toEqual({
        name: 'minimal',
        method: 'minimalMethod',
        batch: undefined,
      });
      expect(ctor.__schedules[0]).toEqual({
        operation: 'minimalMethod',
        cronPattern: '* * * * *',
      });
    });
  });

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

      const ctor = DisabledHandler as any;
      expect(ctor.__disabled).toBe(true);
      expect(ctor.__handlerName).toBe('disabled-handler');
    });

    it('should work without Handler decorator', () => {
      @Disabled()
      class JustDisabled extends BaseHandler {}

      const ctor = JustDisabled as any;
      expect(ctor.__disabled).toBe(true);
    });

    it('should work with context parameter', () => {
      const DisabledClass = Disabled()(class TestClass extends BaseHandler {}, { kind: 'class' });

      const ctor = DisabledClass as any;
      expect(ctor.__disabled).toBe(true);
    });
  });

  describe('Decorator Combinations', () => {
    it('should handle all decorators on one class', () => {
      @Handler('full-handler')
      class FullHandler extends BaseHandler {
        @Operation('simple-op')
        simpleMethod() {}

        @Operation('batch-op', { batch: { enabled: true, size: 50 } })
        batchMethod() {}

        @QueueSchedule('*/15 * * * *', { priority: 5 })
        scheduledOnly() {}

        @ScheduledOperation('combined', '0 0 * * *', {
          immediately: true,
          batch: { enabled: true },
        })
        combinedMethod() {}
      }

      const ctor = FullHandler as any;

      // Handler metadata
      expect(ctor.__handlerName).toBe('full-handler');
      expect(ctor.__needsAutoRegistration).toBe(true);

      // Operations (3 total - simple, batch, and combined)
      expect(ctor.__operations).toHaveLength(3);
      expect(ctor.__operations.map((op: any) => op.name)).toEqual([
        'simple-op',
        'batch-op',
        'combined',
      ]);

      // Schedules (2 total - scheduledOnly and combined)
      expect(ctor.__schedules).toHaveLength(2);
      expect(ctor.__schedules.map((s: any) => s.operation)).toEqual([
        'scheduledOnly',
        'combinedMethod',
      ]);
    });

    it('should handle disabled handler with operations', () => {
      @Disabled()
      @Handler('disabled-with-ops')
      class DisabledWithOps extends BaseHandler {
        @Operation('op1')
        method1() {}

        @QueueSchedule('* * * * *')
        scheduled() {}
      }

      const ctor = DisabledWithOps as any;
      expect(ctor.__disabled).toBe(true);
      expect(ctor.__handlerName).toBe('disabled-with-ops');
      expect(ctor.__operations).toHaveLength(1);
      expect(ctor.__schedules).toHaveLength(1);
    });
  });

  describe('Edge Cases with Method Names', () => {
    it('should handle special method names', () => {
      class TestHandler extends BaseHandler {
        @Operation('toString-op')
        toString() {
          return 'test';
        }

        @Operation('valueOf-op')
        valueOf() {
          return 42;
        }

        @Operation('hasOwnProperty-op')
        hasOwnProperty(v: string | symbol): boolean {
          return super.hasOwnProperty(v);
        }
      }

      const ctor = TestHandler as any;
      expect(ctor.__operations.map((op: any) => op.method)).toEqual([
        'toString',
        'valueOf',
        'hasOwnProperty',
      ]);
    });
  });
});
