import { Test, TestingModule } from '@nestjs/testing';
import { GetOnlineCountHandler } from '../../../../src/application/queries/get-online-count/get-online-count.handler';
import { GetOnlineCountQuery } from '../../../../src/application/queries/get-online-count/get-online-count.query';
import { PresenceRedisRepository } from '../../../../src/infrastructure/redis/presence-redis.repository';
import { OnlineDetectionService } from '../../../../src/domain/services/online-detection.service';
import { TimeWindow } from '../../../../src/domain/value-objects/time-window.vo';

describe('GetOnlineCountHandler', () => {
  let handler: GetOnlineCountHandler;
  let presenceRedisRepository: jest.Mocked<PresenceRedisRepository>;
  let onlineDetectionService: OnlineDetectionService;

  beforeEach(async () => {
    const mockPresenceRedisRepository = {
      updateUserPresence: jest.fn(),
      countOnlineUsers: jest.fn(),
      getOnlineUsers: jest.fn(),
      removeExpiredUsers: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        GetOnlineCountHandler,
        {
          provide: PresenceRedisRepository,
          useValue: mockPresenceRedisRepository,
        },
        OnlineDetectionService,
      ],
    }).compile();

    handler = module.get<GetOnlineCountHandler>(GetOnlineCountHandler);
    presenceRedisRepository = module.get(PresenceRedisRepository);
    onlineDetectionService = module.get(OnlineDetectionService);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('execute', () => {
    it('should return online count', async () => {
      presenceRedisRepository.countOnlineUsers.mockResolvedValue(1500);

      const query = new GetOnlineCountQuery();
      const result = await handler.execute(query);

      expect(result.count).toBe(1500);
    });

    it('should return window seconds from OnlineDetectionService', async () => {
      presenceRedisRepository.countOnlineUsers.mockResolvedValue(100);

      const query = new GetOnlineCountQuery();
      const result = await handler.execute(query);

      expect(result.windowSeconds).toBe(TimeWindow.DEFAULT_ONLINE_WINDOW_SECONDS);
    });

    it('should return queriedAt timestamp', async () => {
      presenceRedisRepository.countOnlineUsers.mockResolvedValue(100);

      const beforeQuery = new Date();
      const query = new GetOnlineCountQuery();
      const result = await handler.execute(query);
      const afterQuery = new Date();

      expect(result.queriedAt).toBeInstanceOf(Date);
      expect(result.queriedAt.getTime()).toBeGreaterThanOrEqual(beforeQuery.getTime());
      expect(result.queriedAt.getTime()).toBeLessThanOrEqual(afterQuery.getTime());
    });

    it('should call countOnlineUsers with correct threshold', async () => {
      presenceRedisRepository.countOnlineUsers.mockResolvedValue(500);

      const query = new GetOnlineCountQuery();
      await handler.execute(query);

      expect(presenceRedisRepository.countOnlineUsers).toHaveBeenCalledWith(
        expect.any(Number),
      );

      // Verify threshold is approximately (now - 180 seconds)
      const calledThreshold = presenceRedisRepository.countOnlineUsers.mock.calls[0][0];
      const expectedThreshold = Math.floor(Date.now() / 1000) - 180;

      expect(Math.abs(calledThreshold - expectedThreshold)).toBeLessThanOrEqual(1);
    });

    it('should handle zero online users', async () => {
      presenceRedisRepository.countOnlineUsers.mockResolvedValue(0);

      const query = new GetOnlineCountQuery();
      const result = await handler.execute(query);

      expect(result.count).toBe(0);
    });

    it('should handle large number of online users', async () => {
      presenceRedisRepository.countOnlineUsers.mockResolvedValue(1000000);

      const query = new GetOnlineCountQuery();
      const result = await handler.execute(query);

      expect(result.count).toBe(1000000);
    });

    it('should throw error when Redis fails', async () => {
      presenceRedisRepository.countOnlineUsers.mockRejectedValue(
        new Error('Redis connection timeout'),
      );

      const query = new GetOnlineCountQuery();

      await expect(handler.execute(query)).rejects.toThrow('Redis connection timeout');
    });
  });
});
