import { Test, TestingModule } from '@nestjs/testing';
import { PluggyEventsController } from './pluggy-events.controller';
import {
  PluggyEventsService,
  CreateEventDto,
  UpdateEventDto,
} from './pluggy-events.service';
import { ServiceCredentialGuard } from '../common/guards/service-credential/service-credential.guard';
import { UserJwtGuard } from '../auth/guards/user-jwt.guard';
import { AuthenticatedUser } from '../auth/interfaces/jwt-payload.interface';

describe('PluggyEventsController', () => {
  let controller: PluggyEventsController;
  let service: PluggyEventsService;

  const mockUser: AuthenticatedUser = {
    id: 'user_123',
    type: 'user',
    payload: {
      sub: 'user_123',
      iss: 'test-issuer',
      prv: 'users',
      iat: 1640995200,
      exp: 1672531200,
      nbf: 1640995200,
      jti: 'jwt-id-123',
    },
    tokenData: { userId: 'user_123' },
    expiresAt: new Date('2024-12-31'),
    issuedAt: new Date('2024-01-01'),
    rawToken: 'test-token',
  };

  const mockClient: AuthenticatedUser = {
    id: 'client_123',
    type: 'client',
    payload: {
      sub: 'client_123',
      iss: 'test-issuer',
      prv: 'hash123',
      iat: 1640995200,
      exp: 1672531200,
      nbf: 1640995200,
      jti: 'jwt-id-456',
    },
    tokenData: { clientId: 'client_123' },
    expiresAt: new Date('2024-12-31'),
    issuedAt: new Date('2024-01-01'),
    rawToken: 'client-token',
  };

  beforeEach(async () => {
    const mockService = {
      create: jest.fn(),
      findAll: jest.fn(),
      findOne: jest.fn(),
      update: jest.fn(),
      remove: jest.fn(),
      getEventsByType: jest.fn(),
      getEventsByItem: jest.fn(),
      getEventsByUser: jest.fn(),
      getEventStats: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      controllers: [PluggyEventsController],
      providers: [
        {
          provide: PluggyEventsService,
          useValue: mockService,
        },
      ],
    })
      .overrideGuard(ServiceCredentialGuard)
      .useValue({ canActivate: () => true })
      .overrideGuard(UserJwtGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get<PluggyEventsController>(PluggyEventsController);
    service = module.get<PluggyEventsService>(PluggyEventsService);
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });

  describe('create', () => {
    it('should create a new event', async () => {
      const createEventDto: CreateEventDto = {
        event: 'ITEM_UPDATED',
        payload: { item: { id: 'item_123' } },
        itemId: 'item_123',
        userId: 'user_123',
      };

      const expectedEvent = {
        _id: 'event_123',
        ...createEventDto,
        receivedAt: new Date(),
      };

      jest.spyOn(service, 'create').mockResolvedValue(expectedEvent);

      const result = await controller.create(createEventDto);

      expect(service.create).toHaveBeenCalledWith(createEventDto);
      expect(result).toEqual(expectedEvent);
    });
  });

  describe('findAll', () => {
    it('should return all events with no filters', async () => {
      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'findAll').mockResolvedValue(mockResult);

      const result = await controller.findAll({});

      expect(service.findAll).toHaveBeenCalledWith({});
      expect(result).toEqual(mockResult);
    });

    it('should return events with string filters', async () => {
      const filters = {
        event: 'ITEM_UPDATED',
        itemId: 'item_123',
        userId: 'user_123',
        connectorId: 'connector_123',
        accountId: 'account_123',
        status: 'processed',
      };

      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'findAll').mockResolvedValue(mockResult);

      const result = await controller.findAll(filters);

      expect(service.findAll).toHaveBeenCalledWith({
        event: 'ITEM_UPDATED',
        itemId: 'item_123',
        userId: 'user_123',
        connectorId: 'connector_123',
        accountId: 'account_123',
        status: 'processed',
      });
      expect(result).toEqual(mockResult);
    });

    it('should handle date filters', async () => {
      const filters = {
        startDate: '2024-01-01',
        endDate: '2024-01-31',
      };

      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'findAll').mockResolvedValue(mockResult);

      const result = await controller.findAll(filters);

      expect(service.findAll).toHaveBeenCalledWith({
        startDate: new Date('2024-01-01'),
        endDate: new Date('2024-01-31'),
      });
      expect(result).toEqual(mockResult);
    });

    it('should handle numeric filters', async () => {
      const filters = {
        limit: '5',
        offset: '10',
      };

      const mockResult = {
        events: [],
        total: 0,
        limit: 5,
        offset: 10,
      };

      jest.spyOn(service, 'findAll').mockResolvedValue(mockResult);

      const result = await controller.findAll(filters);

      expect(service.findAll).toHaveBeenCalledWith({
        limit: 5,
        offset: 10,
      });
      expect(result).toEqual(mockResult);
    });

    it('should ignore empty string filters', async () => {
      const filters = {
        event: '',
        itemId: 'item_123',
        userId: '',
      };

      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'findAll').mockResolvedValue(mockResult);

      const result = await controller.findAll(filters);

      expect(service.findAll).toHaveBeenCalledWith({
        itemId: 'item_123',
      });
      expect(result).toEqual(mockResult);
    });

    it('should ignore invalid date filters', async () => {
      const filters = {
        startDate: 'invalid-date',
        endDate: '2024-01-31',
      };

      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'findAll').mockResolvedValue(mockResult);

      const result = await controller.findAll(filters);

      expect(service.findAll).toHaveBeenCalledWith({
        endDate: new Date('2024-01-31'),
      });
      expect(result).toEqual(mockResult);
    });
  });

  describe('getStats', () => {
    it('should return event statistics', async () => {
      const filters = {
        event: 'ITEM_UPDATED',
        startDate: '2024-01-01',
        endDate: '2024-01-31',
      };

      const mockStats = {
        totalEvents: 100,
        eventsByType: [
          { event: 'ITEM_UPDATED', count: 50 },
          { event: 'ITEM_CREATED', count: 30 },
        ],
        recentEvents: 10,
        period: {
          startDate: new Date('2024-01-01'),
          endDate: new Date('2024-01-31'),
        },
      };

      jest.spyOn(service, 'getEventStats').mockResolvedValue(mockStats);

      const result = await controller.getStats(filters);

      expect(service.getEventStats).toHaveBeenCalledWith({
        event: 'ITEM_UPDATED',
        startDate: new Date('2024-01-01'),
        endDate: new Date('2024-01-31'),
      });
      expect(result).toEqual(mockStats);
    });
  });

  describe('getEventsByType', () => {
    it('should return events by type with query parameter', async () => {
      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'getEventsByType').mockResolvedValue(mockResult);

      const result = await controller.getEventsByType('ITEM_UPDATED', 5, 10);

      expect(service.getEventsByType).toHaveBeenCalledWith(
        'ITEM_UPDATED',
        5,
        10,
      );
      expect(result).toEqual(mockResult);
    });

    it('should use default limit and offset', async () => {
      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'getEventsByType').mockResolvedValue(mockResult);

      const result = await controller.getEventsByType('ITEM_UPDATED');

      expect(service.getEventsByType).toHaveBeenCalledWith(
        'ITEM_UPDATED',
        10,
        0,
      );
      expect(result).toEqual(mockResult);
    });

    it('should throw error when eventType is not provided', async () => {
      try {
        await controller.getEventsByType('');
        fail('Expected error to be thrown');
      } catch (error) {
        expect(error.message).toBe('Event type is required');
      }
    });
  });

  describe('getEventsByTypePath', () => {
    it('should return events by type with path parameter', async () => {
      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'getEventsByType').mockResolvedValue(mockResult);

      const result = await controller.getEventsByTypePath(
        'ITEM_UPDATED',
        5,
        10,
      );

      expect(service.getEventsByType).toHaveBeenCalledWith(
        'ITEM_UPDATED',
        5,
        10,
      );
      expect(result).toEqual(mockResult);
    });
  });

  describe('getEventsByItem', () => {
    it('should return events by item', async () => {
      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'getEventsByItem').mockResolvedValue(mockResult);

      const result = await controller.getEventsByItem('item_123', 5, 10);

      expect(service.getEventsByItem).toHaveBeenCalledWith('item_123', 5, 10);
      expect(result).toEqual(mockResult);
    });
  });

  describe('getEventsByUser', () => {
    it('should return events by user for authorized user', async () => {
      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'getEventsByUser').mockResolvedValue(mockResult);

      const result = await controller.getEventsByUser(
        'user_123',
        5,
        10,
        mockUser,
      );

      expect(service.getEventsByUser).toHaveBeenCalledWith('user_123', 5, 10);
      expect(result).toEqual(mockResult);
    });

    it('should allow client to access any user events', async () => {
      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'getEventsByUser').mockResolvedValue(mockResult);

      const result = await controller.getEventsByUser(
        'user_456',
        5,
        10,
        mockClient,
      );

      expect(service.getEventsByUser).toHaveBeenCalledWith('user_456', 5, 10);
      expect(result).toEqual(mockResult);
    });

    it('should throw error when user tries to access other user events', async () => {
      try {
        await controller.getEventsByUser('user_456', 5, 10, mockUser);
        fail('Expected error to be thrown');
      } catch (error) {
        expect(error.message).toBe('Unauthorized access to other user events');
      }
    });
  });

  describe('getMyEvents', () => {
    it('should return current user events', async () => {
      const mockResult = {
        events: [],
        total: 0,
        limit: 10,
        offset: 0,
      };

      jest.spyOn(service, 'getEventsByUser').mockResolvedValue(mockResult);

      const result = await controller.getMyEvents(mockUser, 5, 10);

      expect(service.getEventsByUser).toHaveBeenCalledWith('user_123', 5, 10);
      expect(result).toEqual(mockResult);
    });
  });

  describe('findOne', () => {
    it('should return an event by id', async () => {
      const mockEvent = {
        _id: 'event_123',
        event: 'ITEM_UPDATED',
        payload: { item: { id: 'item_123' } },
      };

      jest.spyOn(service, 'findOne').mockResolvedValue(mockEvent as any);

      const result = await controller.findOne('event_123');

      expect(service.findOne).toHaveBeenCalledWith('event_123');
      expect(result).toEqual(mockEvent);
    });
  });

  describe('update', () => {
    it('should update an event', async () => {
      const updateEventDto: UpdateEventDto = {
        status: 'processed',
        message: 'Event processed successfully',
      };

      const updatedEvent = {
        _id: 'event_123',
        event: 'ITEM_UPDATED',
        ...updateEventDto,
      };

      jest.spyOn(service, 'update').mockResolvedValue(updatedEvent as any);

      const result = await controller.update('event_123', updateEventDto);

      expect(service.update).toHaveBeenCalledWith('event_123', updateEventDto);
      expect(result).toEqual(updatedEvent);
    });
  });

  describe('remove', () => {
    it('should remove an event', async () => {
      const mockEvent = {
        _id: 'event_123',
        event: 'ITEM_UPDATED',
      };

      jest.spyOn(service, 'remove').mockResolvedValue(mockEvent as any);

      const result = await controller.remove('event_123');

      expect(service.remove).toHaveBeenCalledWith('event_123');
      expect(result).toEqual(mockEvent);
    });
  });
});
