import { EventBusService, Event } from '../EventBusService';
import db from '../../config/database';

// Mock dependencies
jest.mock('../../config/database');
jest.mock('../BullMQService', () => ({
  BullMQService: {
    addJob: jest.fn().mockResolvedValue({}),
    getQueueSize: jest.fn().mockResolvedValue(0)
  }
}));

jest.mock('../RedisService');

const mockDb = db as jest.Mocked<typeof db>;

describe('EventBusService', () => {
  beforeEach(async () => {
    // Reset mocks
    jest.clearAllMocks();
    
    // Mock schema methods
    mockDb.schema.hasTable.mockResolvedValue(false);
    mockDb.schema.createTable.mockResolvedValue(undefined);
    
    // Mock query methods
    mockDb.mockImplementation(() => ({
      insert: jest.fn().mockResolvedValue([1]),
      select: jest.fn().mockResolvedValue([]),
      where: jest.fn().mockReturnThis(),
      orderBy: jest.fn().mockReturnThis(),
      del: jest.fn().mockResolvedValue(0),
      update: jest.fn().mockResolvedValue(1),
      count: jest.fn().mockReturnThis(),
      first: jest.fn().mockResolvedValue({ count: '0' })
    })) as any;
    
    mockDb.raw.mockResolvedValue(undefined);
  });

  describe('initTable', () => {
    it('should create necessary tables if they don\'t exist', async () => {
      await EventBusService.initTable();
      
      expect(mockDb.schema.hasTable).toHaveBeenCalledTimes(4);
      expect(mockDb.schema.createTable).toHaveBeenCalledTimes(4);
    });
  });

  describe('publish', () => {
    it('should publish an event successfully', async () => {
      const eventData = {
        type: 'order.created',
        data: { orderId: 'ORD-123', amount: 100 },
        metadata: {
          source: 'order-service',
          correlationId: 'CORR-123'
        }
      };

      const result = await EventBusService.publish(eventData);
      
      expect(result.id).toBeDefined();
      expect(result.type).toBe(eventData.type);
      expect(result.data).toEqual(eventData.data);
      expect(mockDb).toHaveBeenCalledWith('cf_events');
    });

    it('should throw error when publishing fails', async () => {
      mockDb.mockImplementation(() => ({
        insert: jest.fn().mockRejectedValue(new Error('Database error'))
      })) as any;

      const eventData = {
        type: 'order.created',
        data: { orderId: 'ORD-123' },
        metadata: { source: 'order-service' }
      };

      await expect(EventBusService.publish(eventData)).rejects.toThrow('Database error');
    });
  });

  describe('subscribe and unsubscribe', () => {
    it('should subscribe to an event', async () => {
      const handler = jest.fn();
      const subscription = await EventBusService.subscribe('order.created', handler);
      
      expect(subscription.id).toBeDefined();
      expect(subscription.topic).toBe('order.created');
    });

    it('should unsubscribe from an event', async () => {
      const handler = jest.fn();
      const subscription = await EventBusService.subscribe('order.created', handler);
      
      await EventBusService.unsubscribe(subscription);
      // No error should be thrown
    });
  });

  describe('registerRouteRule', () => {
    it('should register a route rule', async () => {
      const rule = {
        topic: 'order.created',
        condition: { 'data.amount': { $gt: 50 } },
        destinations: ['payment-service', 'inventory-service']
      };

      const result = await EventBusService.registerRouteRule(rule);
      
      expect(result.id).toBeDefined();
      expect(result.topic).toBe(rule.topic);
      expect(result.destinations).toEqual(rule.destinations);
    });
  });

  describe('getEventHistory', () => {
    it('should return events based on filter', async () => {
      const mockEvents = [
        {
          id: 'EVT-1',
          type: 'order.created',
          data: { orderId: 'ORD-123' },
          metadata: { source: 'order-service' },
          created_at: new Date()
        }
      ];

      mockDb.mockImplementation(() => ({
        select: jest.fn().mockReturnThis(),
        where: jest.fn().mockReturnThis(),
        orderBy: jest.fn().mockResolvedValue(mockEvents)
      })) as any;

      const result = await EventBusService.getEventHistory({ type: 'order.created' });
      
      expect(result).toHaveLength(1);
      expect(result[0].type).toBe('order.created');
    });
  });

  describe('healthCheck', () => {
    it('should return healthy status', async () => {
      mockDb.raw.mockResolvedValue(undefined);
      mockDb.mockImplementation(() => ({
        count: jest.fn().mockReturnThis(),
        first: jest.fn().mockResolvedValue({ count: '10' })
      })) as any;

      const result = await EventBusService.healthCheck();
      
      expect(result.status).toBe('healthy');
      expect(result.metrics.eventCount).toBe(10);
    });

    it('should return unhealthy status on error', async () => {
      mockDb.raw.mockRejectedValue(new Error('Database connection error'));

      const result = await EventBusService.healthCheck();
      
      expect(result.status).toBe('unhealthy');
      expect(result.message).toBe('Database connection error');
    });
  });

  describe('retryDeadLetter', () => {
    it('should retry a dead letter event', async () => {
      const deadLetter = {
        id: 'DLQ-1',
        event_id: 'EVT-1',
        topic: 'order.created',
        event_data: {
          type: 'order.created',
          data: { orderId: 'ORD-123' },
          metadata: { source: 'order-service' }
        },
        error: 'Processing error',
        retry_count: 0,
        created_at: new Date(),
        updated_at: new Date()
      };

      mockDb.mockImplementation((table: string) => {
        if (table === 'cf_event_dead_letters') {
          return {
            where: jest.fn().mockReturnThis(),
            first: jest.fn().mockResolvedValue(deadLetter),
            update: jest.fn().mockResolvedValue(1)
          };
        }
        return {
          insert: jest.fn().mockResolvedValue([1])
        };
      }) as any;

      const result = await EventBusService.retryDeadLetter('DLQ-1');
      
      expect(result).toBe(true);
    });

    it('should return false if dead letter not found', async () => {
      mockDb.mockImplementation((table: string) => {
        if (table === 'cf_event_dead_letters') {
          return {
            where: jest.fn().mockReturnThis(),
            first: jest.fn().mockResolvedValue(null)
          };
        }
        return {};
      }) as any;

      const result = await EventBusService.retryDeadLetter('DLQ-1');
      
      expect(result).toBe(false);
    });
  });

  describe('cleanExpiredEvents', () => {
    it('should clean expired events', async () => {
      mockDb.mockImplementation(() => ({
        where: jest.fn().mockReturnThis(),
        del: jest.fn().mockResolvedValue(5)
      })) as any;

      const result = await EventBusService.cleanExpiredEvents(30);
      
      expect(result).toBe(5);
    });
  });
});
