import { describe, expect, it } from 'vitest';
import { RacingDomainInvariantError, RacingDomainValidationError } from '../../errors/RacingDomainError';
import { Penalty } from './Penalty';

describe('Penalty', () => {
  describe('create', () => {
    it('should create a penalty with required fields', () => {
      const penalty = Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'time_penalty',
        value: 5,
        reason: 'Speeding',
        issuedBy: 'steward-1',
      });

      expect(penalty.id.toString()).toBe('penalty-1');
      expect(penalty.leagueId).toBe('league-1');
      expect(penalty.raceId).toBe('race-1');
      expect(penalty.driverId).toBe('driver-1');
      expect(penalty.type).toBe('time_penalty');
      expect(penalty.value).toBe(5);
      expect(penalty.reason).toBe('Speeding');
      expect(penalty.issuedBy).toBe('steward-1');
      expect(penalty.status).toBe('pending');
      expect(penalty.appliedAt).toBeUndefined();
      expect(penalty.notes).toBeUndefined();
    });

    it('should create a penalty with all fields', () => {
      const issuedAt = new Date('2023-01-01');
      const appliedAt = new Date('2023-01-02');
      const penalty = Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'disqualification',
        reason: 'Unsafe driving',
        protestId: 'protest-1',
        issuedBy: 'steward-1',
        status: 'applied',
        issuedAt,
        appliedAt,
        notes: 'Applied after review',
      });

      expect(penalty.id.toString()).toBe('penalty-1');
      expect(penalty.type).toBe('disqualification');
      expect(penalty.value).toBeUndefined();
      expect(penalty.protestId).toBe('protest-1');
      expect(penalty.status).toBe('applied');
      expect(penalty.issuedAt).toEqual(issuedAt);
      expect(penalty.appliedAt).toEqual(appliedAt);
      expect(penalty.notes).toBe('Applied after review');
    });

    it('should throw error for invalid id', () => {
      expect(() => Penalty.create({
        id: '',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'time_penalty',
        value: 5,
        reason: 'Speeding',
        issuedBy: 'steward-1',
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid type', () => {
      expect(() => Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'invalid_type',
        value: 5,
        reason: 'Speeding',
        issuedBy: 'steward-1',
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for missing value on time_penalty', () => {
      expect(() => Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'time_penalty',
        reason: 'Speeding',
        issuedBy: 'steward-1',
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for zero value', () => {
      expect(() => Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'time_penalty',
        value: 0,
        reason: 'Speeding',
        issuedBy: 'steward-1',
      })).toThrow(RacingDomainValidationError);
    });
  });

  describe('isPending', () => {
    it('should return true for pending status', () => {
      const penalty = Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'warning',
        reason: 'Warning',
        issuedBy: 'steward-1',
      });
      expect(penalty.isPending()).toBe(true);
    });

    it('should return false for applied status', () => {
      const penalty = Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'warning',
        reason: 'Warning',
        issuedBy: 'steward-1',
        status: 'applied',
      });
      expect(penalty.isPending()).toBe(false);
    });
  });

  describe('markAsApplied', () => {
    it('should mark penalty as applied', () => {
      const penalty = Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'warning',
        reason: 'Warning',
        issuedBy: 'steward-1',
      });
      const applied = penalty.markAsApplied('Applied successfully');
      expect(applied.status).toBe('applied');
      expect(applied.appliedAt).toBeInstanceOf(Date);
      expect(applied.notes).toBe('Applied successfully');
    });

    it('should throw error if already applied', () => {
      const penalty = Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'warning',
        reason: 'Warning',
        issuedBy: 'steward-1',
        status: 'applied',
      });
      expect(() => penalty.markAsApplied()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('overturn', () => {
    it('should overturn the penalty', () => {
      const penalty = Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'warning',
        reason: 'Warning',
        issuedBy: 'steward-1',
      });
      const overturned = penalty.overturn('Appealed successfully');
      expect(overturned.status).toBe('overturned');
      expect(overturned.notes).toBe('Appealed successfully');
    });

    it('should throw error if already overturned', () => {
      const penalty = Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'warning',
        reason: 'Warning',
        issuedBy: 'steward-1',
        status: 'overturned',
      });
      expect(() => penalty.overturn('Reason')).toThrow(RacingDomainInvariantError);
    });
  });

  describe('getDescription', () => {
    it('should return description for time_penalty', () => {
      const penalty = Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'time_penalty',
        value: 5,
        reason: 'Speeding',
        issuedBy: 'steward-1',
      });
      expect(penalty.getDescription()).toBe('+5s time penalty');
    });

    it('should return description for disqualification', () => {
      const penalty = Penalty.create({
        id: 'penalty-1',
        leagueId: 'league-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        type: 'disqualification',
        reason: 'Unsafe',
        issuedBy: 'steward-1',
      });
      expect(penalty.getDescription()).toBe('Disqualified from race');
    });
  });
});