import { beforeEach, describe, expect, it, mock } from 'bun:test';
import type { ExecutionContext, IServiceContainer } from '@stock-bot/types';
import { BaseHandler } from '../src/base/BaseHandler';

// Test handler with metadata
class ConfigTestHandler extends BaseHandler {
  static __handlerName = 'config-test';
  static __operations = [
    { name: 'process', method: 'processData' },
    { name: 'validate', method: 'validateData' },
  ];
  static __schedules = [
    {
      operation: 'processData',
      cronPattern: '0 * * * *',
      priority: 5,
      immediately: false,
      description: 'Hourly processing',
      payload: { type: 'scheduled' },
      batch: { size: 100 },
    },
  ];
  static __description = 'Test handler for configuration';

  async processData(input: any, context: ExecutionContext) {
    return { processed: true, input };
  }

  async validateData(input: any, context: ExecutionContext) {
    return { valid: true, input };
  }
}

// Handler without metadata
class NoMetadataHandler extends BaseHandler {}

describe('BaseHandler Configuration', () => {
  let mockServices: IServiceContainer;

  beforeEach(() => {
    mockServices = {
      cache: null,
      globalCache: null,
      queueManager: null,
      proxy: null,
      browser: null,
      mongodb: null,
      postgres: null,
      questdb: null,
    } as any;
  });

  describe('createHandlerConfig', () => {
    it('should create handler config from metadata', () => {
      const handler = new ConfigTestHandler(mockServices);
      const config = handler.createHandlerConfig();

      expect(config.name).toBe('config-test');
      expect(Object.keys(config.operations)).toEqual(['process', 'validate']);
      expect(config.scheduledJobs).toHaveLength(1);
    });

    it('should create job handlers for operations', () => {
      const handler = new ConfigTestHandler(mockServices);
      const config = handler.createHandlerConfig();

      expect(typeof config.operations.process).toBe('function');
      expect(typeof config.operations.validate).toBe('function');
    });

    it('should include scheduled job details', () => {
      const handler = new ConfigTestHandler(mockServices);
      const config = handler.createHandlerConfig();

      const scheduledJob = config.scheduledJobs[0];
      expect(scheduledJob.type).toBe('config-test-processData');
      expect(scheduledJob.operation).toBe('process');
      expect(scheduledJob.cronPattern).toBe('0 * * * *');
      expect(scheduledJob.priority).toBe(5);
      expect(scheduledJob.immediately).toBe(false);
      expect(scheduledJob.description).toBe('Hourly processing');
      expect(scheduledJob.payload).toEqual({ type: 'scheduled' });
      expect(scheduledJob.batch).toEqual({ size: 100 });
    });

    it('should execute operations through job handlers', async () => {
      const handler = new ConfigTestHandler(mockServices);
      const config = handler.createHandlerConfig();

      // Mock the job execution
      const processJob = config.operations.process;
      const result = await processJob({ data: 'test' }, {} as any);

      expect(result).toEqual({ processed: true, input: { data: 'test' } });
    });

    it('should throw error when no metadata found', () => {
      const handler = new NoMetadataHandler(mockServices);

      expect(() => handler.createHandlerConfig()).toThrow('Handler metadata not found');
    });

    it('should handle schedule without matching operation', () => {
      class ScheduleOnlyHandler extends BaseHandler {
        static __handlerName = 'schedule-only';
        static __operations = [];
        static __schedules = [
          {
            operation: 'nonExistentMethod',
            cronPattern: '* * * * *',
          },
        ];
      }

      const handler = new ScheduleOnlyHandler(mockServices);
      const config = handler.createHandlerConfig();

      expect(config.operations).toEqual({});
      expect(config.scheduledJobs).toHaveLength(1);
      expect(config.scheduledJobs[0].operation).toBe('nonExistentMethod');
    });

    it('should handle empty schedules array', () => {
      class NoScheduleHandler extends BaseHandler {
        static __handlerName = 'no-schedule';
        static __operations = [{ name: 'test', method: 'testMethod' }];
        static __schedules = [];

        testMethod() {}
      }

      const handler = new NoScheduleHandler(mockServices);
      const config = handler.createHandlerConfig();

      expect(config.scheduledJobs).toEqual([]);
      expect(config.operations).toHaveProperty('test');
    });

    it('should create execution context with proper metadata', async () => {
      const handler = new ConfigTestHandler(mockServices);
      const config = handler.createHandlerConfig();

      // Spy on execute method
      const executeSpy = mock();
      handler.execute = executeSpy;
      executeSpy.mockResolvedValue({ result: 'test' });

      // Execute through job handler
      await config.operations.process({ input: 'data' }, {} as any);

      expect(executeSpy).toHaveBeenCalledWith(
        'process',
        { input: 'data' },
        expect.objectContaining({
          type: 'queue',
          metadata: expect.objectContaining({
            source: 'queue',
            timestamp: expect.any(Number),
          }),
        })
      );
    });
  });

  describe('extractMetadata', () => {
    it('should extract complete metadata', () => {
      const metadata = ConfigTestHandler.extractMetadata();

      expect(metadata).not.toBeNull();
      expect(metadata?.name).toBe('config-test');
      expect(metadata?.operations).toEqual(['process', 'validate']);
      expect(metadata?.description).toBe('Test handler for configuration');
      expect(metadata?.scheduledJobs).toHaveLength(1);
    });

    it('should return null for handler without metadata', () => {
      const metadata = NoMetadataHandler.extractMetadata();
      expect(metadata).toBeNull();
    });

    it('should handle missing optional fields', () => {
      class MinimalHandler extends BaseHandler {
        static __handlerName = 'minimal';
        static __operations = [];
      }

      const metadata = MinimalHandler.extractMetadata();

      expect(metadata).not.toBeNull();
      expect(metadata?.name).toBe('minimal');
      expect(metadata?.operations).toEqual([]);
      expect(metadata?.scheduledJobs).toEqual([]);
      expect(metadata?.description).toBeUndefined();
    });

    it('should map schedule operations correctly', () => {
      class MappedScheduleHandler extends BaseHandler {
        static __handlerName = 'mapped';
        static __operations = [
          { name: 'op1', method: 'method1' },
          { name: 'op2', method: 'method2' },
        ];
        static __schedules = [
          { operation: 'method1', cronPattern: '* * * * *' },
          { operation: 'method2', cronPattern: '0 * * * *' },
        ];
      }

      const metadata = MappedScheduleHandler.extractMetadata();

      expect(metadata?.scheduledJobs[0].operation).toBe('op1');
      expect(metadata?.scheduledJobs[1].operation).toBe('op2');
    });
  });
});
