/**
 * Tests for GetTeamRatingLedgerQuery
 */

import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import { TeamRatingEvent } from '../../domain/entities/TeamRatingEvent';
import type { TeamRatingEventRepository } from '../../domain/repositories/TeamRatingEventRepository';
import { TeamRatingDelta } from '../../domain/value-objects/TeamRatingDelta';
import { TeamRatingDimensionKey } from '../../domain/value-objects/TeamRatingDimensionKey';
import { TeamRatingEventId } from '../../domain/value-objects/TeamRatingEventId';
import { GetTeamRatingLedgerQuery, GetTeamRatingLedgerQueryHandler } from './GetTeamRatingLedgerQuery';

describe('GetTeamRatingLedgerQuery', () => {
  let mockRatingEventRepo: { findEventsPaginated: Mock };
  let handler: GetTeamRatingLedgerQueryHandler;

  beforeEach(() => {
    mockRatingEventRepo = {
      findEventsPaginated: vi.fn(),
    };

    handler = new GetTeamRatingLedgerQueryHandler(mockRatingEventRepo as unknown as TeamRatingEventRepository);
  });

  describe('execute', () => {
    it('should return paginated ledger entries', async () => {
      const teamId = 'team-123';

      // Mock paginated result
      const event1 = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId,
        dimension: TeamRatingDimensionKey.create('driving'),
        delta: TeamRatingDelta.create(10),
        occurredAt: new Date('2024-01-01T10:00:00Z'),
        createdAt: new Date('2024-01-01T10:00:00Z'),
        source: { type: 'race', id: 'race-123' },
        reason: { code: 'RACE_FINISH' },
        visibility: { public: true },
        version: 1,
      });

      const event2 = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId,
        dimension: TeamRatingDimensionKey.create('adminTrust'),
        delta: TeamRatingDelta.create(-5),
        occurredAt: new Date('2024-01-02T10:00:00Z'),
        createdAt: new Date('2024-01-02T10:00:00Z'),
        source: { type: 'penalty', id: 'penalty-456' },
        reason: { code: 'LATE_JOIN' },
        visibility: { public: true },
        version: 1,
        weight: 2,
      });

      mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
        items: [event1, event2],
        total: 2,
        limit: 20,
        offset: 0,
        hasMore: false,
        nextOffset: undefined,
      });

      const query: GetTeamRatingLedgerQuery = { teamId };
      const result = await handler.execute(query);

      expect(result.entries.length).toBe(2);
      
      const entry1 = result.entries[0];
      expect(entry1).toBeDefined();
      if (entry1) {
        expect(entry1.teamId).toBe(teamId);
        expect(entry1.dimension).toBe('driving');
        expect(entry1.delta).toBe(10);
        expect(entry1.source.type).toBe('race');
        expect(entry1.source.id).toBe('race-123');
        expect(entry1.reason.code).toBe('RACE_FINISH');
        expect(entry1.visibility.public).toBe(true);
      }

      const entry2 = result.entries[1];
      expect(entry2).toBeDefined();
      if (entry2) {
        expect(entry2.dimension).toBe('adminTrust');
        expect(entry2.delta).toBe(-5);
        expect(entry2.weight).toBe(2);
        expect(entry2.source.type).toBe('penalty');
        expect(entry2.source.id).toBe('penalty-456');
      }

      expect(result.pagination.total).toBe(2);
      expect(result.pagination.limit).toBe(20);
      expect(result.pagination.hasMore).toBe(false);
    });

    it('should apply default pagination values', async () => {
      const teamId = 'team-123';

      mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
        items: [],
        total: 0,
        limit: 20,
        offset: 0,
        hasMore: false,
      });

      const query: GetTeamRatingLedgerQuery = { teamId };
      await handler.execute(query);

      expect(mockRatingEventRepo.findEventsPaginated).toHaveBeenCalledWith(
        teamId,
        expect.objectContaining({
          limit: 20,
          offset: 0,
        })
      );
    });

    it('should apply custom pagination values', async () => {
      const teamId = 'team-123';

      mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
        items: [],
        total: 0,
        limit: 10,
        offset: 20,
        hasMore: true,
        nextOffset: 30,
      });

      const query: GetTeamRatingLedgerQuery = { teamId, limit: 10, offset: 20 };
      await handler.execute(query);

      expect(mockRatingEventRepo.findEventsPaginated).toHaveBeenCalledWith(
        teamId,
        expect.objectContaining({
          limit: 10,
          offset: 20,
        })
      );
    });

    it('should apply filters when provided', async () => {
      const teamId = 'team-123';
      const filter = {
        dimensions: ['driving'],
        sourceTypes: ['race', 'penalty'] as ('race' | 'penalty' | 'vote' | 'adminAction' | 'manualAdjustment')[],
        from: '2024-01-01T00:00:00Z',
        to: '2024-01-31T23:59:59Z',
        reasonCodes: ['RACE_FINISH', 'LATE_JOIN'],
      };

      mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
        items: [],
        total: 0,
        limit: 20,
        offset: 0,
        hasMore: false,
      });

      const query: GetTeamRatingLedgerQuery = { teamId, filter };
      await handler.execute(query);

      expect(mockRatingEventRepo.findEventsPaginated).toHaveBeenCalledWith(
        teamId,
        expect.objectContaining({
          filter: expect.objectContaining({
            dimensions: ['driving'],
            sourceTypes: ['race', 'penalty'],
            from: new Date('2024-01-01T00:00:00Z'),
            to: new Date('2024-01-31T23:59:59Z'),
            reasonCodes: ['RACE_FINISH', 'LATE_JOIN'],
          }),
        })
      );
    });

    it('should handle events with optional weight', async () => {
      const teamId = 'team-123';

      const eventWithWeight = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId,
        dimension: TeamRatingDimensionKey.create('driving'),
        delta: TeamRatingDelta.create(15),
        weight: 1.5,
        occurredAt: new Date('2024-01-01T10:00:00Z'),
        createdAt: new Date('2024-01-01T10:00:00Z'),
        source: { type: 'race', id: 'race-789' },
        reason: { code: 'PERFORMANCE_BONUS' },
        visibility: { public: true },
        version: 1,
      });

      const eventWithoutWeight = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId,
        dimension: TeamRatingDimensionKey.create('adminTrust'),
        delta: TeamRatingDelta.create(5),
        occurredAt: new Date('2024-01-02T10:00:00Z'),
        createdAt: new Date('2024-01-02T10:00:00Z'),
        source: { type: 'vote', id: 'vote-123' },
        reason: { code: 'POSITIVE_VOTE' },
        visibility: { public: true },
        version: 1,
      });

      mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
        items: [eventWithWeight, eventWithoutWeight],
        total: 2,
        limit: 20,
        offset: 0,
        hasMore: false,
      });

      const query: GetTeamRatingLedgerQuery = { teamId };
      const result = await handler.execute(query);

      const entry1 = result.entries[0];
      const entry2 = result.entries[1];
      expect(entry1).toBeDefined();
      expect(entry2).toBeDefined();
      
      if (entry1) {
        expect(entry1.weight).toBe(1.5);
      }
      if (entry2) {
        expect(entry2.weight).toBeUndefined();
      }
    });

    it('should handle events with optional source.id', async () => {
      const teamId = 'team-123';

      const eventWithId = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId,
        dimension: TeamRatingDimensionKey.create('driving'),
        delta: TeamRatingDelta.create(10),
        occurredAt: new Date('2024-01-01T10:00:00Z'),
        createdAt: new Date('2024-01-01T10:00:00Z'),
        source: { type: 'race', id: 'race-123' },
        reason: { code: 'RACE_FINISH' },
        visibility: { public: true },
        version: 1,
      });

      const eventWithoutId = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId,
        dimension: TeamRatingDimensionKey.create('adminTrust'),
        delta: TeamRatingDelta.create(5),
        occurredAt: new Date('2024-01-02T10:00:00Z'),
        createdAt: new Date('2024-01-02T10:00:00Z'),
        source: { type: 'manualAdjustment' },
        reason: { code: 'ADMIN_ADJUSTMENT' },
        visibility: { public: true },
        version: 1,
      });

      mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
        items: [eventWithId, eventWithoutId],
        total: 2,
        limit: 20,
        offset: 0,
        hasMore: false,
      });

      const query: GetTeamRatingLedgerQuery = { teamId };
      const result = await handler.execute(query);

      const entry1 = result.entries[0];
      const entry2 = result.entries[1];
      expect(entry1).toBeDefined();
      expect(entry2).toBeDefined();
      
      if (entry1) {
        expect(entry1.source.id).toBe('race-123');
      }
      if (entry2) {
        expect(entry2.source.id).toBeUndefined();
      }
    });

    it('should handle events with optional reason.description', async () => {
      const teamId = 'team-123';

      const eventWithDescription = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId,
        dimension: TeamRatingDimensionKey.create('driving'),
        delta: TeamRatingDelta.create(10),
        occurredAt: new Date('2024-01-01T10:00:00Z'),
        createdAt: new Date('2024-01-01T10:00:00Z'),
        source: { type: 'race', id: 'race-123' },
        reason: { code: 'RACE_FINISH', description: 'Finished 1st in class' },
        visibility: { public: true },
        version: 1,
      });

      const eventWithoutDescription = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId,
        dimension: TeamRatingDimensionKey.create('adminTrust'),
        delta: TeamRatingDelta.create(5),
        occurredAt: new Date('2024-01-02T10:00:00Z'),
        createdAt: new Date('2024-01-02T10:00:00Z'),
        source: { type: 'vote', id: 'vote-123' },
        reason: { code: 'POSITIVE_VOTE' },
        visibility: { public: true },
        version: 1,
      });

      mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
        items: [eventWithDescription, eventWithoutDescription],
        total: 2,
        limit: 20,
        offset: 0,
        hasMore: false,
      });

      const query: GetTeamRatingLedgerQuery = { teamId };
      const result = await handler.execute(query);

      const entry1 = result.entries[0];
      const entry2 = result.entries[1];
      expect(entry1).toBeDefined();
      expect(entry2).toBeDefined();
      
      if (entry1) {
        expect(entry1.reason.description).toBe('Finished 1st in class');
      }
      if (entry2) {
        expect(entry2.reason.description).toBeUndefined();
      }
    });

    it('should return nextOffset when hasMore is true', async () => {
      const teamId = 'team-123';

      mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
        items: [],
        total: 50,
        limit: 20,
        offset: 20,
        hasMore: true,
        nextOffset: 40,
      });

      const query: GetTeamRatingLedgerQuery = { teamId };
      const result = await handler.execute(query);

      expect(result.pagination.hasMore).toBe(true);
      expect(result.pagination.nextOffset).toBe(40);
    });

    it('should return null nextOffset when hasMore is false', async () => {
      const teamId = 'team-123';

      mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
        items: [],
        total: 15,
        limit: 20,
        offset: 0,
        hasMore: false,
      });

      const query: GetTeamRatingLedgerQuery = { teamId };
      const result = await handler.execute(query);

      expect(result.pagination.hasMore).toBe(false);
      expect(result.pagination.nextOffset).toBeNull();
    });
  });
});