import { describe, it, expect, vi } from 'vitest';
import { SendFinalResultsUseCase } from './SendFinalResultsUseCase';
import type { INotificationService } from '../../../notifications/application/ports/INotificationService';
import type { IRaceEventRepository } from '../../domain/repositories/IRaceEventRepository';
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
import type { RaceEventStewardingClosedEvent } from '../../domain/events/RaceEventStewardingClosed';

describe('SendFinalResultsUseCase', () => {
  it('sends final results notifications to all participating drivers', async () => {
    const mockNotificationService = {
      sendNotification: vi.fn(),
    } as unknown as INotificationService;

    const mockRaceEvent = {
      id: 'race-1',
      name: 'Test Race',
      getMainRaceSession: vi.fn().mockReturnValue({ id: 'session-1' }),
    };

    const mockRaceEventRepository = {
      findById: vi.fn().mockResolvedValue(mockRaceEvent),
    } as unknown as IRaceEventRepository;

    const mockResults = [
      {
        driverId: 'driver-1',
        position: 1,
        incidents: 0,
        getPositionChange: vi.fn().mockReturnValue(2),
      },
      {
        driverId: 'driver-2',
        position: 2,
        incidents: 1,
        getPositionChange: vi.fn().mockReturnValue(-1),
      },
    ];

    const mockResultRepository = {
      findByRaceId: vi.fn().mockResolvedValue(mockResults),
    } as unknown as IResultRepository;

    const useCase = new SendFinalResultsUseCase(
      mockNotificationService,
      mockRaceEventRepository,
      mockResultRepository,
    );

    const event: RaceEventStewardingClosedEvent = {
      eventType: 'RaceEventStewardingClosed',
      aggregateId: 'race-1',
      occurredAt: new Date(),
      eventData: {
        raceEventId: 'race-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        driverIds: ['driver-1', 'driver-2'],
        hadPenaltiesApplied: false,
        closedAt: new Date(),
      },
    };

    const result = await useCase.execute(event);

    expect(result.isOk()).toBe(true);
    expect(mockRaceEventRepository.findById).toHaveBeenCalledWith('race-1');
    expect(mockResultRepository.findByRaceId).toHaveBeenCalledWith('session-1');
    expect(mockNotificationService.sendNotification).toHaveBeenCalledTimes(2);

    // Check first notification
    expect(mockNotificationService.sendNotification).toHaveBeenCalledWith(
      expect.objectContaining({
        recipientId: 'driver-1',
        type: 'race_final_results',
        title: 'Final Results: Test Race',
        body: expect.stringContaining('Final result: P1 (+2 positions). Clean race! +35 rating.'),
        data: expect.objectContaining({
          raceEventId: 'race-1',
          sessionId: 'session-1',
          leagueId: 'league-1',
          position: 1,
          positionChange: 2,
          incidents: 0,
          finalRatingChange: 35,
          hadPenaltiesApplied: false,
        }),
      }),
    );

    // Check second notification
    expect(mockNotificationService.sendNotification).toHaveBeenCalledWith(
      expect.objectContaining({
        recipientId: 'driver-2',
        type: 'race_final_results',
        title: 'Final Results: Test Race',
        body: expect.stringContaining('Final result: P2 (-1 positions). 1 incident +20 rating.'),
        data: expect.objectContaining({
          position: 2,
          positionChange: -1,
          incidents: 1,
          finalRatingChange: 20,
        }),
      }),
    );
  });

  it('skips sending notifications if race event not found', async () => {
    const mockNotificationService = {
      sendNotification: vi.fn(),
    } as unknown as INotificationService;

    const mockRaceEventRepository = {
      findById: vi.fn().mockResolvedValue(null),
    } as unknown as IRaceEventRepository;

    const mockResultRepository = {
      findByRaceId: vi.fn(),
    } as unknown as IResultRepository;

    const useCase = new SendFinalResultsUseCase(
      mockNotificationService,
      mockRaceEventRepository,
      mockResultRepository,
    );

    const event: RaceEventStewardingClosedEvent = {
      eventType: 'RaceEventStewardingClosed',
      aggregateId: 'race-1',
      occurredAt: new Date(),
      eventData: {
        raceEventId: 'race-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        driverIds: ['driver-1'],
        hadPenaltiesApplied: false,
        closedAt: new Date(),
      },
    };

    const result = await useCase.execute(event);

    expect(result.isOk()).toBe(true);
    expect(mockNotificationService.sendNotification).not.toHaveBeenCalled();
  });

  it('skips sending notifications if no main race session', async () => {
    const mockNotificationService = {
      sendNotification: vi.fn(),
    } as unknown as INotificationService;

    const mockRaceEvent = {
      id: 'race-1',
      name: 'Test Race',
      getMainRaceSession: vi.fn().mockReturnValue(null),
    };

    const mockRaceEventRepository = {
      findById: vi.fn().mockResolvedValue(mockRaceEvent),
    } as unknown as IRaceEventRepository;

    const mockResultRepository = {
      findByRaceId: vi.fn(),
    } as unknown as IResultRepository;

    const useCase = new SendFinalResultsUseCase(
      mockNotificationService,
      mockRaceEventRepository,
      mockResultRepository,
    );

    const event: RaceEventStewardingClosedEvent = {
      eventType: 'RaceEventStewardingClosed',
      aggregateId: 'race-1',
      occurredAt: new Date(),
      eventData: {
        raceEventId: 'race-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        driverIds: ['driver-1'],
        hadPenaltiesApplied: false,
        closedAt: new Date(),
      },
    };

    const result = await useCase.execute(event);

    expect(result.isOk()).toBe(true);
    expect(mockNotificationService.sendNotification).not.toHaveBeenCalled();
  });
});