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

describe('Protest', () => {
  describe('create', () => {
    it('should create a protest with required fields', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
      });

      expect(protest.id).toBe('protest-1');
      expect(protest.raceId).toBe('race-1');
      expect(protest.protestingDriverId).toBe('driver-1');
      expect(protest.accusedDriverId).toBe('driver-2');
      expect(protest.incident.lap.toNumber()).toBe(5);
      expect(protest.incident.description.toString()).toBe('Unsafe overtake');
      expect(protest.status.toString()).toBe('pending');
      expect(protest.filedAt).toBeInstanceOf(Date);
    });

    it('should create a protest with all fields', () => {
      const filedAt = new Date('2023-01-01');
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake', timeInRace: 120.5 },
        comment: 'He cut me off',
        proofVideoUrl: 'https://example.com/video.mp4',
        status: 'under_review',
        reviewedBy: 'steward-1',
        decisionNotes: 'Reviewed and upheld',
        filedAt,
        reviewedAt: new Date('2023-01-02'),
        defense: { statement: 'It was a racing incident', videoUrl: 'https://example.com/defense.mp4', submittedAt: new Date('2023-01-01T12:00:00Z') },
        defenseRequestedAt: new Date('2023-01-01T10:00:00Z'),
        defenseRequestedBy: 'steward-1',
      });

      expect(protest.id).toBe('protest-1');
      expect(protest.comment).toBe('He cut me off');
      expect(protest.proofVideoUrl).toBe('https://example.com/video.mp4');
      expect(protest.status.toString()).toBe('under_review');
      expect(protest.reviewedBy).toBe('steward-1');
      expect(protest.decisionNotes).toBe('Reviewed and upheld');
      expect(protest.filedAt).toEqual(filedAt);
      expect(protest.defense).toBeDefined();
      expect(protest.defenseRequestedAt).toBeInstanceOf(Date);
      expect(protest.defenseRequestedBy).toBe('steward-1');
    });

    it('should throw error for invalid id', () => {
      expect(() => Protest.create({
        id: '',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid lap', () => {
      expect(() => Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: -1, description: 'Unsafe overtake' },
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for empty description', () => {
      expect(() => Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: '' },
      })).toThrow(RacingDomainValidationError);
    });
  });

  describe('requestDefense', () => {
    it('should request defense for pending protest', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
      });
      const updated = protest.requestDefense('steward-1');
      expect(updated.status.toString()).toBe('awaiting_defense');
      expect(updated.defenseRequestedAt).toBeInstanceOf(Date);
      expect(updated.defenseRequestedBy).toBe('steward-1');
    });

    it('should throw error if not pending', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
        status: 'upheld',
      });
      expect(() => protest.requestDefense('steward-1')).toThrow(RacingDomainInvariantError);
    });
  });

  describe('submitDefense', () => {
    it('should submit defense for awaiting defense protest', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
        status: 'awaiting_defense',
      });
      const updated = protest.submitDefense('It was a racing incident');
      expect(updated.status.toString()).toBe('under_review');
      expect(updated.defense).toBeDefined();
      if (updated.defense) {
        expect(updated.defense.statement.toString()).toBe('It was a racing incident');
      }
    });

    it('should throw error if not awaiting defense', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
      });
      expect(() => protest.submitDefense('Statement')).toThrow(RacingDomainInvariantError);
    });
  });

  describe('uphold', () => {
    it('should uphold pending protest', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
      });
      const updated = protest.uphold('steward-1', 'Penalty applied');
      expect(updated.status.toString()).toBe('upheld');
      expect(updated.reviewedBy).toBe('steward-1');
      expect(updated.decisionNotes).toBe('Penalty applied');
      expect(updated.reviewedAt).toBeInstanceOf(Date);
    });

    it('should throw error if resolved', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
        status: 'upheld',
      });
      expect(() => protest.uphold('steward-1', 'Notes')).toThrow(RacingDomainInvariantError);
    });
  });

  describe('withdraw', () => {
    it('should withdraw pending protest', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
      });
      const updated = protest.withdraw();
      expect(updated.status.toString()).toBe('withdrawn');
      expect(updated.reviewedAt).toBeInstanceOf(Date);
    });

    it('should throw error if resolved', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
        status: 'upheld',
      });
      expect(() => protest.withdraw()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('isPending', () => {
    it('should return true for pending status', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
      });
      expect(protest.isPending()).toBe(true);
    });

    it('should return false for upheld status', () => {
      const protest = Protest.create({
        id: 'protest-1',
        raceId: 'race-1',
        protestingDriverId: 'driver-1',
        accusedDriverId: 'driver-2',
        incident: { lap: 5, description: 'Unsafe overtake' },
        status: 'upheld',
      });
      expect(protest.isPending()).toBe(false);
    });
  });
});