import { Test, TestingModule } from '@nestjs/testing';
import { GetOnlineHistoryHandler } from '../../../../src/application/queries/get-online-history/get-online-history.handler';
import { GetOnlineHistoryQuery } from '../../../../src/application/queries/get-online-history/get-online-history.query';
import {
  IOnlineSnapshotRepository,
  ONLINE_SNAPSHOT_REPOSITORY,
} from '../../../../src/domain/repositories/online-snapshot.repository.interface';
import { OnlineSnapshot } from '../../../../src/domain/entities/online-snapshot.entity';

describe('GetOnlineHistoryHandler', () => {
  let handler: GetOnlineHistoryHandler;
  let snapshotRepository: jest.Mocked<IOnlineSnapshotRepository>;

  const createSnapshot = (ts: Date, onlineCount: number) =>
    OnlineSnapshot.reconstitute({
      id: BigInt(Math.floor(Math.random() * 1000000)),
      ts,
      onlineCount,
      windowSeconds: 180,
    });

  beforeEach(async () => {
    const mockSnapshotRepository: jest.Mocked<IOnlineSnapshotRepository> = {
      insert: jest.fn(),
      findByTimeRange: jest.fn(),
      findLatest: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        GetOnlineHistoryHandler,
        {
          provide: ONLINE_SNAPSHOT_REPOSITORY,
          useValue: mockSnapshotRepository,
        },
      ],
    }).compile();

    handler = module.get<GetOnlineHistoryHandler>(GetOnlineHistoryHandler);
    snapshotRepository = module.get(ONLINE_SNAPSHOT_REPOSITORY);
  });

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

  describe('execute', () => {
    it('should return online history data', async () => {
      const startTime = new Date('2025-01-01T00:00:00.000Z');
      const endTime = new Date('2025-01-01T01:00:00.000Z');

      const snapshots = [
        createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 1000),
        createSnapshot(new Date('2025-01-01T00:05:00.000Z'), 1100),
        createSnapshot(new Date('2025-01-01T00:10:00.000Z'), 1200),
      ];

      snapshotRepository.findByTimeRange.mockResolvedValue(snapshots);

      const query = new GetOnlineHistoryQuery(startTime, endTime, '5m');
      const result = await handler.execute(query);

      expect(result.data.length).toBe(3);
      expect(result.interval).toBe('5m');
      expect(result.startTime).toBe(startTime.toISOString());
      expect(result.endTime).toBe(endTime.toISOString());
    });

    it('should aggregate data by interval', async () => {
      const startTime = new Date('2025-01-01T00:00:00.000Z');
      const endTime = new Date('2025-01-01T00:10:00.000Z');

      // Snapshots within same 5-minute bucket
      const snapshots = [
        createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 1000),
        createSnapshot(new Date('2025-01-01T00:01:00.000Z'), 1100),
        createSnapshot(new Date('2025-01-01T00:02:00.000Z'), 1200),
        createSnapshot(new Date('2025-01-01T00:05:00.000Z'), 1500),
        createSnapshot(new Date('2025-01-01T00:06:00.000Z'), 1600),
      ];

      snapshotRepository.findByTimeRange.mockResolvedValue(snapshots);

      const query = new GetOnlineHistoryQuery(startTime, endTime, '5m');
      const result = await handler.execute(query);

      // Should aggregate into 2 buckets: [00:00-00:05) and [00:05-00:10)
      expect(result.data.length).toBe(2);

      // First bucket average: (1000 + 1100 + 1200) / 3 = 1100
      expect(result.data[0].onlineCount).toBe(1100);

      // Second bucket average: (1500 + 1600) / 2 = 1550
      expect(result.data[1].onlineCount).toBe(1550);
    });

    it('should calculate summary statistics', async () => {
      const startTime = new Date('2025-01-01T00:00:00.000Z');
      const endTime = new Date('2025-01-01T01:00:00.000Z');

      const snapshots = [
        createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 500),
        createSnapshot(new Date('2025-01-01T00:05:00.000Z'), 1000),
        createSnapshot(new Date('2025-01-01T00:10:00.000Z'), 2000),
        createSnapshot(new Date('2025-01-01T00:15:00.000Z'), 1500),
      ];

      snapshotRepository.findByTimeRange.mockResolvedValue(snapshots);

      const query = new GetOnlineHistoryQuery(startTime, endTime, '5m');
      const result = await handler.execute(query);

      expect(result.summary.maxOnline).toBe(2000);
      expect(result.summary.minOnline).toBe(500);
      expect(result.summary.avgOnline).toBe(1250); // (500+1000+2000+1500)/4
      expect(result.summary.maxTimestamp).toBe('2025-01-01T00:10:00.000Z');
      expect(result.summary.minTimestamp).toBe('2025-01-01T00:00:00.000Z');
    });

    it('should handle empty data', async () => {
      snapshotRepository.findByTimeRange.mockResolvedValue([]);

      const query = new GetOnlineHistoryQuery(
        new Date('2025-01-01T00:00:00.000Z'),
        new Date('2025-01-01T01:00:00.000Z'),
        '5m',
      );
      const result = await handler.execute(query);

      expect(result.data.length).toBe(0);
      expect(result.total).toBe(0);
      expect(result.summary.maxOnline).toBe(0);
      expect(result.summary.minOnline).toBe(0);
      expect(result.summary.avgOnline).toBe(0);
      expect(result.summary.maxTimestamp).toBeNull();
      expect(result.summary.minTimestamp).toBeNull();
    });

    it('should use default 5m interval', async () => {
      snapshotRepository.findByTimeRange.mockResolvedValue([]);

      const query = new GetOnlineHistoryQuery(
        new Date('2025-01-01T00:00:00.000Z'),
        new Date('2025-01-01T01:00:00.000Z'),
      );
      const result = await handler.execute(query);

      expect(result.interval).toBe('5m');
    });

    it('should support 1m interval', async () => {
      const snapshots = [
        createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 1000),
        createSnapshot(new Date('2025-01-01T00:00:30.000Z'), 1100),
        createSnapshot(new Date('2025-01-01T00:01:00.000Z'), 1200),
      ];

      snapshotRepository.findByTimeRange.mockResolvedValue(snapshots);

      const query = new GetOnlineHistoryQuery(
        new Date('2025-01-01T00:00:00.000Z'),
        new Date('2025-01-01T00:02:00.000Z'),
        '1m',
      );
      const result = await handler.execute(query);

      expect(result.interval).toBe('1m');
      // Should aggregate into 2 buckets
      expect(result.data.length).toBe(2);
    });

    it('should support 1h interval', async () => {
      const snapshots = [
        createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 1000),
        createSnapshot(new Date('2025-01-01T00:30:00.000Z'), 1200),
        createSnapshot(new Date('2025-01-01T01:00:00.000Z'), 1500),
      ];

      snapshotRepository.findByTimeRange.mockResolvedValue(snapshots);

      const query = new GetOnlineHistoryQuery(
        new Date('2025-01-01T00:00:00.000Z'),
        new Date('2025-01-01T02:00:00.000Z'),
        '1h',
      );
      const result = await handler.execute(query);

      expect(result.interval).toBe('1h');
      // Should aggregate into 2 buckets: [00:00-01:00) and [01:00-02:00)
      expect(result.data.length).toBe(2);
    });

    it('should include total count in result', async () => {
      const snapshots = [
        createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 1000),
        createSnapshot(new Date('2025-01-01T00:05:00.000Z'), 1100),
        createSnapshot(new Date('2025-01-01T00:10:00.000Z'), 1200),
      ];

      snapshotRepository.findByTimeRange.mockResolvedValue(snapshots);

      const query = new GetOnlineHistoryQuery(
        new Date('2025-01-01T00:00:00.000Z'),
        new Date('2025-01-01T00:15:00.000Z'),
        '5m',
      );
      const result = await handler.execute(query);

      expect(result.total).toBe(result.data.length);
    });

    it('should throw error when repository fails', async () => {
      snapshotRepository.findByTimeRange.mockRejectedValue(
        new Error('Database connection failed'),
      );

      const query = new GetOnlineHistoryQuery(
        new Date('2025-01-01T00:00:00.000Z'),
        new Date('2025-01-01T01:00:00.000Z'),
        '5m',
      );

      await expect(handler.execute(query)).rejects.toThrow('Database connection failed');
    });
  });
});
