import { describe, expect, it } from 'vitest';
import { RacingDomainInvariantError, RacingDomainValidationError } from '../errors/RacingDomainError';
import { SessionType } from '../value-objects/SessionType';
import { Race } from './Race';

describe('Race', () => {
  describe('create', () => {
    it('should create a race with required fields', () => {
      const scheduledAt = new Date('2023-01-01T10:00:00Z');
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
      });

      expect(race.id).toBe('race-1');
      expect(race.leagueId).toBe('league-1');
      expect(race.scheduledAt).toEqual(scheduledAt);
      expect(race.track).toBe('Monza');
      expect(race.car).toBe('Ferrari SF21');
      expect(race.sessionType).toEqual(SessionType.main());
      expect(race.status.toString()).toBe('scheduled');
      expect(race.trackId).toBeUndefined();
      expect(race.carId).toBeUndefined();
      expect(race.strengthOfField).toBeUndefined();
      expect(race.registeredCount).toBeUndefined();
      expect(race.maxParticipants).toBeUndefined();
    });

    it('should create a race with all fields', () => {
      const scheduledAt = new Date('2023-01-01T10:00:00Z');
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        trackId: 'track-1',
        car: 'Ferrari SF21',
        carId: 'car-1',
        sessionType: SessionType.qualifying(),
        status: 'running',
        strengthOfField: 1500,
        registeredCount: 20,
        maxParticipants: 24,
      });

      expect(race.id).toBe('race-1');
      expect(race.leagueId).toBe('league-1');
      expect(race.scheduledAt).toEqual(scheduledAt);
      expect(race.track).toBe('Monza');
      expect(race.trackId).toBe('track-1');
      expect(race.car).toBe('Ferrari SF21');
      expect(race.carId).toBe('car-1');
      expect(race.sessionType).toEqual(SessionType.qualifying());
      expect(race.status.toString()).toBe('running');
      expect(race.strengthOfField?.toNumber()).toBe(1500);
      expect(race.registeredCount?.toNumber()).toBe(20);
      expect(race.maxParticipants?.toNumber()).toBe(24);
    });

    it('should throw error for invalid id', () => {
      const scheduledAt = new Date();
      expect(() => Race.create({
        id: '',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid leagueId', () => {
      const scheduledAt = new Date();
      expect(() => Race.create({
        id: 'race-1',
        leagueId: '',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid scheduledAt', () => {
      expect(() => Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt: 'invalid' as unknown as Date,
        track: 'Monza',
        car: 'Ferrari SF21',
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid track', () => {
      const scheduledAt = new Date();
      expect(() => Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: '',
        car: 'Ferrari SF21',
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid car', () => {
      const scheduledAt = new Date();
      expect(() => Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: '',
      })).toThrow(RacingDomainValidationError);
    });
  });

  describe('start', () => {
    it('should start a scheduled race', () => {
      const scheduledAt = new Date(Date.now() + 3600000); // future
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'scheduled',
      });
      const started = race.start();
      expect(started.status.toString()).toBe('running');
    });

    it('should throw error if not scheduled', () => {
      const scheduledAt = new Date();
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'running',
      });
      expect(() => race.start()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('complete', () => {
    it('should complete a running race', () => {
      const scheduledAt = new Date();
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'running',
      });
      const completed = race.complete();
      expect(completed.status.toString()).toBe('completed');
    });

    it('should throw error if already completed', () => {
      const scheduledAt = new Date();
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'completed',
      });
      expect(() => race.complete()).toThrow(RacingDomainInvariantError);
    });

    it('should throw error if cancelled', () => {
      const scheduledAt = new Date();
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'cancelled',
      });
      expect(() => race.complete()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('cancel', () => {
    it('should cancel a scheduled race', () => {
      const scheduledAt = new Date(Date.now() + 3600000);
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'scheduled',
      });
      const cancelled = race.cancel();
      expect(cancelled.status.toString()).toBe('cancelled');
    });

    it('should throw error if completed', () => {
      const scheduledAt = new Date();
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'completed',
      });
      expect(() => race.cancel()).toThrow(RacingDomainInvariantError);
    });

    it('should throw error if already cancelled', () => {
      const scheduledAt = new Date();
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'cancelled',
      });
      expect(() => race.cancel()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('updateField', () => {
    it('should update strengthOfField and registeredCount', () => {
      const scheduledAt = new Date();
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
      });
      const updated = race.updateField(1600, 22);
      expect(updated.strengthOfField?.toNumber()).toBe(1600);
      expect(updated.registeredCount?.toNumber()).toBe(22);
    });
  });

  describe('isPast', () => {
    it('should return true for past race', () => {
      const scheduledAt = new Date(Date.now() - 3600000); // past
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
      });
      expect(race.isPast()).toBe(true);
    });

    it('should return false for future race', () => {
      const scheduledAt = new Date(Date.now() + 3600000); // future
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
      });
      expect(race.isPast()).toBe(false);
    });
  });

  describe('isUpcoming', () => {
    it('should return true for scheduled future race', () => {
      const scheduledAt = new Date(Date.now() + 3600000); // future
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'scheduled',
      });
      expect(race.isUpcoming()).toBe(true);
    });

    it('should return false for past scheduled race', () => {
      const scheduledAt = new Date(Date.now() - 3600000); // past
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'scheduled',
      });
      expect(race.isUpcoming()).toBe(false);
    });

    it('should return false for running race', () => {
      const scheduledAt = new Date(Date.now() + 3600000);
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'running',
      });
      expect(race.isUpcoming()).toBe(false);
    });
  });

  describe('isLive', () => {
    it('should return true for running race', () => {
      const scheduledAt = new Date();
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'running',
      });
      expect(race.isLive()).toBe(true);
    });

    it('should return false for scheduled race', () => {
      const scheduledAt = new Date();
      const race = Race.create({
        id: 'race-1',
        leagueId: 'league-1',
        scheduledAt,
        track: 'Monza',
        car: 'Ferrari SF21',
        status: 'scheduled',
      });
      expect(race.isLive()).toBe(false);
    });
  });
});