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

// Test handler implementation
class TestHandler extends BaseHandler {
  testMethod(input: any, context: ExecutionContext) {
    return { result: 'test', input, context };
  }

  async onInit() {
    // Lifecycle hook
  }

  protected getScheduledJobPayload(operation: string) {
    return { scheduled: true, operation };
  }
}

// Handler with no operations
class EmptyHandler extends BaseHandler {}

// Handler with missing method
class BrokenHandler extends BaseHandler {
  constructor(services: IServiceContainer) {
    super(services);
    const ctor = this.constructor as any;
    ctor.__operations = [{ name: 'missing', method: 'nonExistentMethod' }];
  }
}

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

  beforeEach(() => {
    mockServices = {
      cache: {
        get: mock(async () => null),
        set: mock(async () => {}),
        del: mock(async () => {}),
        has: mock(async () => false),
        clear: mock(async () => {}),
        keys: mock(async () => []),
        mget: mock(async () => []),
        mset: mock(async () => {}),
        mdel: mock(async () => {}),
        ttl: mock(async () => -1),
        expire: mock(async () => true),
        getClientType: () => 'redis',
        isConnected: () => true,
      },
      globalCache: null,
      queueManager: {
        getQueue: mock(() => ({
          add: mock(async () => ({})),
          addBulk: mock(async () => []),
          pause: mock(async () => {}),
          resume: mock(async () => {}),
          clean: mock(async () => []),
          drain: mock(async () => {}),
          obliterate: mock(async () => {}),
          close: mock(async () => {}),
          isReady: mock(async () => true),
          isClosed: () => false,
          name: 'test-queue',
        })),
      },
      proxy: null,
      browser: null,
      mongodb: null,
      postgres: null,
      questdb: null,
    } as any;
  });

  describe('Constructor Edge Cases', () => {
    it('should handle handler without decorator metadata', () => {
      const handler = new TestHandler(mockServices);
      expect(handler).toBeInstanceOf(BaseHandler);
    });

    it('should use provided handler name', () => {
      const handler = new TestHandler(mockServices, 'custom-handler');
      expect(handler).toBeInstanceOf(BaseHandler);
    });

    it('should handle null queue manager', () => {
      const servicesWithoutQueue = { ...mockServices, queueManager: null };
      const handler = new TestHandler(servicesWithoutQueue);
      expect(handler.queue).toBeUndefined();
    });
  });

  describe('Execute Method Edge Cases', () => {
    it('should throw for unknown operation', async () => {
      const handler = new TestHandler(mockServices);
      const context: ExecutionContext = { type: 'queue', metadata: {} };

      await expect(handler.execute('unknownOp', {}, context)).rejects.toThrow(
        'Unknown operation: unknownOp'
      );
    });

    it('should handle operation with no operations metadata', async () => {
      const handler = new EmptyHandler(mockServices);
      const context: ExecutionContext = { type: 'queue', metadata: {} };

      await expect(handler.execute('anyOp', {}, context)).rejects.toThrow(
        'Unknown operation: anyOp'
      );
    });

    it('should throw when method is not a function', async () => {
      const handler = new BrokenHandler(mockServices);
      const context: ExecutionContext = { type: 'queue', metadata: {} };

      await expect(handler.execute('missing', {}, context)).rejects.toThrow(
        "Operation method 'nonExistentMethod' not found on handler"
      );
    });

    it('should execute operation with proper context', async () => {
      const handler = new TestHandler(mockServices);
      const ctor = handler.constructor as any;
      ctor.__operations = [{ name: 'test', method: 'testMethod' }];

      const context: ExecutionContext = {
        type: 'queue',
        metadata: { source: 'test' },
      };

      const result = await handler.execute('test', { data: 'test' }, context);
      expect(result).toEqual({
        result: 'test',
        input: { data: 'test' },
        context,
      });
    });
  });

  describe('Service Helper Methods Edge Cases', () => {
    it('should handle missing cache service', async () => {
      const servicesWithoutCache = { ...mockServices, cache: null };
      const handler = new TestHandler(servicesWithoutCache);

      // Should not throw, just return gracefully
      await handler['cacheSet']('key', 'value');
      const value = await handler['cacheGet']('key');
      expect(value).toBeNull();

      await handler['cacheDel']('key');
    });

    it('should handle missing global cache service', async () => {
      const handler = new TestHandler(mockServices); // globalCache is already null

      await handler['globalCacheSet']('key', 'value');
      const value = await handler['globalCacheGet']('key');
      expect(value).toBeNull();

      await handler['globalCacheDel']('key');
    });

    it('should handle missing MongoDB service', () => {
      const handler = new TestHandler(mockServices);

      expect(() => handler['collection']('test')).toThrow('MongoDB service is not available');
    });

    it('should schedule operation without queue', async () => {
      const servicesWithoutQueue = { ...mockServices, queueManager: null };
      const handler = new TestHandler(servicesWithoutQueue);

      await expect(handler.scheduleOperation('test', {})).rejects.toThrow(
        'Queue service is not available for this handler'
      );
    });
  });

  describe('Execution Context Creation', () => {
    it('should create execution context with metadata', () => {
      const handler = new TestHandler(mockServices);

      const context = handler['createExecutionContext']('http', { custom: 'data' });

      expect(context.type).toBe('http');
      expect(context.metadata.custom).toBe('data');
      expect(context.metadata.timestamp).toBeDefined();
      expect(context.metadata.traceId).toBeDefined();
      expect(context.metadata.traceId).toContain('TestHandler');
    });

    it('should create execution context without metadata', () => {
      const handler = new TestHandler(mockServices);

      const context = handler['createExecutionContext']('queue');

      expect(context.type).toBe('queue');
      expect(context.metadata.timestamp).toBeDefined();
      expect(context.metadata.traceId).toBeDefined();
    });
  });

  describe('HTTP Helper Edge Cases', () => {
    it('should provide HTTP methods', () => {
      const handler = new TestHandler(mockServices);
      const http = handler['http'];

      expect(http.get).toBeDefined();
      expect(http.post).toBeDefined();
      expect(http.put).toBeDefined();
      expect(http.delete).toBeDefined();

      // All should be functions
      expect(typeof http.get).toBe('function');
      expect(typeof http.post).toBe('function');
      expect(typeof http.put).toBe('function');
      expect(typeof http.delete).toBe('function');
    });
  });

  describe('Static Methods Edge Cases', () => {
    it('should return null for handler without metadata', () => {
      const metadata = TestHandler.extractMetadata();
      expect(metadata).toBeNull();
    });

    it('should extract metadata with all fields', () => {
      const HandlerWithMeta = class extends BaseHandler {
        static __handlerName = 'meta-handler';
        static __operations = [
          { name: 'op1', method: 'method1' },
          { name: 'op2', method: 'method2' },
        ];
        static __schedules = [
          {
            operation: 'method1',
            cronPattern: '* * * * *',
            priority: 10,
            immediately: true,
            description: 'Test schedule',
            payload: { test: true },
            batch: { size: 10 },
          },
        ];
        static __description = 'Test handler description';
      };

      const metadata = HandlerWithMeta.extractMetadata();

      expect(metadata).toBeDefined();
      expect(metadata?.name).toBe('meta-handler');
      expect(metadata?.operations).toEqual(['op1', 'op2']);
      expect(metadata?.description).toBe('Test handler description');
      expect(metadata?.scheduledJobs).toHaveLength(1);

      const job = metadata?.scheduledJobs[0];
      expect(job?.type).toBe('meta-handler-method1');
      expect(job?.operation).toBe('op1');
      expect(job?.cronPattern).toBe('* * * * *');
      expect(job?.priority).toBe(10);
      expect(job?.immediately).toBe(true);
      expect(job?.payload).toEqual({ test: true });
      expect(job?.batch).toEqual({ size: 10 });
    });
  });

  describe('Handler Configuration Creation', () => {
    it('should throw when no metadata found', () => {
      const handler = new TestHandler(mockServices);

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

    it('should create handler config with operations', () => {
      const HandlerWithMeta = class extends BaseHandler {
        static __handlerName = 'config-handler';
        static __operations = [{ name: 'process', method: 'processData' }];
        static __schedules = [];
      };

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

      expect(config.name).toBe('config-handler');
      expect(config.operations.process).toBeDefined();
      expect(typeof config.operations.process).toBe('function');
      expect(config.scheduledJobs).toEqual([]);
    });
  });

  describe('Service Availability Check', () => {
    it('should correctly identify available services', () => {
      const handler = new TestHandler(mockServices);

      expect(handler['hasService']('cache')).toBe(true);
      expect(handler['hasService']('queueManager')).toBe(true);
      expect(handler['hasService']('globalCache')).toBe(false);
      expect(handler['hasService']('mongodb')).toBe(false);
    });
  });

  describe('Scheduled Handler Edge Cases', () => {
    it('should be instance of BaseHandler', () => {
      const handler = new ScheduledHandler(mockServices);
      expect(handler).toBeInstanceOf(BaseHandler);
      expect(handler).toBeInstanceOf(ScheduledHandler);
    });
  });

  describe('Cache Helpers with Namespacing', () => {
    it('should create namespaced cache', () => {
      const handler = new TestHandler(mockServices);
      const nsCache = handler['createNamespacedCache']('api');

      expect(nsCache).toBeDefined();
    });

    it('should prefix cache keys with handler name', async () => {
      const TestHandlerWithName = class extends BaseHandler {
        static __handlerName = 'test-handler';
      };

      const handler = new TestHandlerWithName(mockServices);

      await handler['cacheSet']('mykey', 'value', 3600);

      expect(mockServices.cache?.set).toHaveBeenCalledWith('test-handler:mykey', 'value', 3600);
    });
  });

  describe('Schedule Helper Methods', () => {
    it('should schedule with delay in seconds', async () => {
      const handler = new TestHandler(mockServices);

      // The queue is already set in the handler constructor
      const mockAdd = handler.queue?.add;

      await handler['scheduleIn']('test-op', { data: 'test' }, 30, { priority: 10 });

      expect(mockAdd).toHaveBeenCalledWith(
        'test-op',
        {
          handler: 'testhandler',
          operation: 'test-op',
          payload: { data: 'test' },
        },
        { delay: 30000, priority: 10 }
      );
    });
  });

  describe('Logging Helper', () => {
    it('should log with handler context', () => {
      const handler = new TestHandler(mockServices);

      // The log method should exist
      expect(typeof handler['log']).toBe('function');

      // It should be callable without errors
      expect(() => {
        handler['log']('info', 'Test message', { extra: 'data' });
      }).not.toThrow();
    });
  });
});
