import { describe, it, beforeEach, expect, vi } from 'vitest';
import { Session } from '@core/racing/domain/entities/Session';
import { RaceEvent } from '@core/racing/domain/entities/RaceEvent';
import { SessionType } from '@core/racing/domain/value-objects/SessionType';
import { MainRaceCompletedEvent } from '@core/racing/domain/events/MainRaceCompleted';
import { RaceEventStewardingClosedEvent } from '@core/racing/domain/events/RaceEventStewardingClosed';
import { SendPerformanceSummaryUseCase } from '@core/racing/application/use-cases/SendPerformanceSummaryUseCase';
import { SendFinalResultsUseCase } from '@core/racing/application/use-cases/SendFinalResultsUseCase';
import { CloseRaceEventStewardingUseCase } from '@core/racing/application/use-cases/CloseRaceEventStewardingUseCase';
import { InMemoryRaceEventRepository } from '@core/racing/infrastructure/repositories/InMemoryRaceEventRepository';
import { InMemorySessionRepository } from '@core/racing/infrastructure/repositories/InMemorySessionRepository';

// Mock notification service
const mockNotificationService = {
  sendNotification: vi.fn(),
};

// Test data builders
const createTestSession = (overrides: Partial<{
  id: string;
  raceEventId: string;
  sessionType: SessionType;
  status: 'scheduled' | 'running' | 'completed';
  scheduledAt: Date;
}> = {}) => {
  return Session.create({
    id: overrides.id ?? 'session-1',
    raceEventId: overrides.raceEventId ?? 'race-event-1',
    scheduledAt: overrides.scheduledAt ?? new Date(),
    track: 'Monza',
    car: 'F1 Car',
    sessionType: overrides.sessionType ?? SessionType.main(),
    status: overrides.status ?? 'scheduled',
  });
};

const createTestRaceEvent = (overrides: Partial<{
  id: string;
  seasonId: string;
  leagueId: string;
  name: string;
  sessions: Session[];
  status: 'scheduled' | 'in_progress' | 'awaiting_stewarding' | 'closed';
  stewardingClosesAt: Date;
}> = {}) => {
  const sessions = overrides.sessions ?? [
    createTestSession({ id: 'practice-1', sessionType: SessionType.practice() }),
    createTestSession({ id: 'qualifying-1', sessionType: SessionType.qualifying() }),
    createTestSession({ id: 'main-1', sessionType: SessionType.main() }),
  ];

  return RaceEvent.create({
    id: overrides.id ?? 'race-event-1',
    seasonId: overrides.seasonId ?? 'season-1',
    leagueId: overrides.leagueId ?? 'league-1',
    name: overrides.name ?? 'Monza Grand Prix',
    sessions,
    status: overrides.status ?? 'scheduled',
    stewardingClosesAt: overrides.stewardingClosesAt,
  });
};

describe('Race Event Performance Summary Notifications', () => {
  let raceEventRepository: InMemoryRaceEventRepository;
  let sessionRepository: InMemorySessionRepository;
  let sendPerformanceSummaryUseCase: SendPerformanceSummaryUseCase;
  let sendFinalResultsUseCase: SendFinalResultsUseCase;
  let closeStewardingUseCase: CloseRaceEventStewardingUseCase;

  beforeEach(() => {
    raceEventRepository = new InMemoryRaceEventRepository();
    sessionRepository = new InMemorySessionRepository();
    sendPerformanceSummaryUseCase = new SendPerformanceSummaryUseCase(
      mockNotificationService as any,
      raceEventRepository as any,
      {} as any // Mock result repository
    );
    sendFinalResultsUseCase = new SendFinalResultsUseCase(
      mockNotificationService as any,
      raceEventRepository as any,
      {} as any // Mock result repository
    );
    closeStewardingUseCase = new CloseRaceEventStewardingUseCase(
      raceEventRepository as any,
      {} as any // Mock domain event publisher
    );
    vi.clearAllMocks();
  });

  describe('Performance Summary After Main Race Completion', () => {
    it('should send performance summary notification when main race completes', async () => {
      // Given
      const raceEvent = createTestRaceEvent();
      await raceEventRepository.create(raceEvent);

      const mainRaceCompletedEvent = new MainRaceCompletedEvent({
        raceEventId: raceEvent.id,
        sessionId: 'main-1',
        leagueId: raceEvent.leagueId,
        seasonId: raceEvent.seasonId,
        completedAt: new Date(),
        driverIds: ['driver-1', 'driver-2'],
      });

      // When
      await sendPerformanceSummaryUseCase.execute(mainRaceCompletedEvent);

      // Then
      expect(mockNotificationService.sendNotification).toHaveBeenCalledTimes(2);
      expect(mockNotificationService.sendNotification).toHaveBeenCalledWith(
        expect.objectContaining({
          recipientId: 'driver-1',
          type: 'race_performance_summary',
          urgency: 'modal',
          title: expect.stringContaining('Race Complete'),
        })
      );
    });

    it('should calculate provisional rating changes correctly', async () => {
      // Given
      const raceEvent = createTestRaceEvent();
      await raceEventRepository.create(raceEvent);

      // Mock result repository to return position data
      const mockResultRepository = {
        findByRaceId: vi.fn().mockResolvedValue([
          { driverId: 'driver-1', position: 1, incidents: 0, getPositionChange: () => 0 },
        ]),
      };

      const useCase = new SendPerformanceSummaryUseCase(
        mockNotificationService as any,
        raceEventRepository as any,
        mockResultRepository as any
      );

      const event = new MainRaceCompletedEvent({
        raceEventId: raceEvent.id,
        sessionId: 'main-1',
        leagueId: raceEvent.leagueId,
        seasonId: raceEvent.seasonId,
        completedAt: new Date(),
        driverIds: ['driver-1'],
      });

      // When
      await useCase.execute(event);

      // Then
      expect(mockNotificationService.sendNotification).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            provisionalRatingChange: 25, // P1 with 0 incidents = +25
          }),
        })
      );
    });
  });

  describe('Final Results After Stewarding Closes', () => {
    it('should send final results notification when stewarding closes', async () => {
      // Given
      const raceEvent = createTestRaceEvent({ status: 'awaiting_stewarding' });
      await raceEventRepository.create(raceEvent);

      const stewardingClosedEvent = new RaceEventStewardingClosedEvent({
        raceEventId: raceEvent.id,
        leagueId: raceEvent.leagueId,
        seasonId: raceEvent.seasonId,
        closedAt: new Date(),
        driverIds: ['driver-1', 'driver-2'],
        hadPenaltiesApplied: false,
      });

      // When
      await sendFinalResultsUseCase.execute(stewardingClosedEvent);

      // Then
      expect(mockNotificationService.sendNotification).toHaveBeenCalledTimes(2);
      expect(mockNotificationService.sendNotification).toHaveBeenCalledWith(
        expect.objectContaining({
          recipientId: 'driver-1',
          type: 'race_final_results',
          urgency: 'modal',
          title: expect.stringContaining('Final Results'),
        })
      );
    });
  });

  describe('Stewarding Window Management', () => {
    it('should close expired stewarding windows', async () => {
      // Given
      const pastDate = new Date(Date.now() - 25 * 60 * 60 * 1000); // 25 hours ago
      const raceEvent = createTestRaceEvent({
        status: 'awaiting_stewarding',
        stewardingClosesAt: pastDate,
      });
      await raceEventRepository.create(raceEvent);

      // When
      await closeStewardingUseCase.execute({});

      // Then
      const updatedEvent = await raceEventRepository.findById(raceEvent.id);
      expect(updatedEvent?.status).toBe('closed');
    });

    it('should not close unexpired stewarding windows', async () => {
      // Given
      const futureDate = new Date(Date.now() + 25 * 60 * 60 * 1000); // 25 hours from now
      const raceEvent = createTestRaceEvent({
        status: 'awaiting_stewarding',
        stewardingClosesAt: futureDate,
      });
      await raceEventRepository.create(raceEvent);

      // When
      await closeStewardingUseCase.execute({});

      // Then
      const updatedEvent = await raceEventRepository.findById(raceEvent.id);
      expect(updatedEvent?.status).toBe('awaiting_stewarding');
    });
  });

  describe('Race Event Lifecycle', () => {
    it('should transition from scheduled to in_progress when sessions start', () => {
      // Given
      const raceEvent = createTestRaceEvent({ status: 'scheduled' });

      // When
      const startedEvent = raceEvent.start();

      // Then
      expect(startedEvent.status).toBe('in_progress');
    });

    it('should transition to awaiting_stewarding when main race completes', () => {
      // Given
      const raceEvent = createTestRaceEvent({ status: 'in_progress' });

      // When
      const completedEvent = raceEvent.completeMainRace();

      // Then
      expect(completedEvent.status).toBe('awaiting_stewarding');
    });

    it('should transition to closed when stewarding closes', () => {
      // Given
      const raceEvent = createTestRaceEvent({ status: 'awaiting_stewarding' });

      // When
      const closedEvent = raceEvent.closeStewarding();

      // Then
      expect(closedEvent.status).toBe('closed');
    });
  });

  describe('Session Type Behavior', () => {
    it('should identify main race sessions correctly', () => {
      // Given
      const mainSession = createTestSession({ sessionType: SessionType.main() });
      const practiceSession = createTestSession({ sessionType: SessionType.practice() });

      // Then
      expect(mainSession.countsForPoints()).toBe(true);
      expect(practiceSession.countsForPoints()).toBe(false);
    });

    it('should identify qualifying sessions correctly', () => {
      // Given
      const qualiSession = createTestSession({ sessionType: SessionType.qualifying() });
      const mainSession = createTestSession({ sessionType: SessionType.main() });

      // Then
      expect(qualiSession.determinesGrid()).toBe(true);
      expect(mainSession.determinesGrid()).toBe(false);
    });
  });
});