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

describe('Prize', () => {
  describe('create', () => {
    it('should create a prize with required fields', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
      });

      expect(prize.id.toString()).toBe('prize-1');
      expect(prize.seasonId.toString()).toBe('season-1');
      expect(prize.position.toNumber()).toBe(1);
      expect(prize.amount).toEqual(amount);
      expect(prize.driverId).toBeUndefined();
      expect(prize.status.toString()).toBe('pending');
      expect(prize.awardedAt).toBeUndefined();
      expect(prize.paidAt).toBeUndefined();
      expect(prize.description).toBeUndefined();
    });

    it('should create a prize with all fields', () => {
      const amount = Money.create(2000, 'USD');
      const createdAt = new Date('2023-01-01');
      const awardedAt = new Date('2023-01-02');
      const paidAt = new Date('2023-01-03');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 2,
        amount,
        driverId: 'driver-1',
        status: 'paid',
        createdAt,
        awardedAt,
        paidAt,
        description: 'First place prize',
      });

      expect(prize.id.toString()).toBe('prize-1');
      expect(prize.seasonId.toString()).toBe('season-1');
      expect(prize.position.toNumber()).toBe(2);
      expect(prize.amount).toEqual(amount);
      expect(prize.driverId!.toString()).toBe('driver-1');
      expect(prize.status.toString()).toBe('paid');
      expect(prize.createdAt).toEqual(createdAt);
      expect(prize.awardedAt).toEqual(awardedAt);
      expect(prize.paidAt).toEqual(paidAt);
      expect(prize.description).toBe('First place prize');
    });

    it('should throw error for invalid id', () => {
      const amount = Money.create(1000, 'USD');
      expect(() => Prize.create({
        id: '',
        seasonId: 'season-1',
        position: 1,
        amount,
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid seasonId', () => {
      const amount = Money.create(1000, 'USD');
      expect(() => Prize.create({
        id: 'prize-1',
        seasonId: '',
        position: 1,
        amount,
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid position', () => {
      const amount = Money.create(1000, 'USD');
      expect(() => Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 0,
        amount,
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for non-integer position', () => {
      const amount = Money.create(1000, 'USD');
      expect(() => Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1.5,
        amount,
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for zero amount', () => {
      const amount = Money.create(0, 'USD');
      expect(() => Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
      })).toThrow(RacingDomainValidationError);
    });
  });

  describe('awardTo', () => {
    it('should award prize to a driver', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
      });
      const awarded = prize.awardTo('driver-1');
      expect(awarded.driverId!.toString()).toBe('driver-1');
      expect(awarded.status.toString()).toBe('awarded');
      expect(awarded.awardedAt).toBeInstanceOf(Date);
    });

    it('should throw error for empty driverId', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
      });
      expect(() => prize.awardTo('')).toThrow(RacingDomainValidationError);
    });

    it('should throw error if not pending', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
        status: 'awarded',
      });
      expect(() => prize.awardTo('driver-1')).toThrow(RacingDomainInvariantError);
    });
  });

  describe('markAsPaid', () => {
    it('should mark prize as paid', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
        driverId: 'driver-1',
        status: 'awarded',
      });
      const paid = prize.markAsPaid();
      expect(paid.status.toString()).toBe('paid');
      expect(paid.paidAt).toBeInstanceOf(Date);
    });

    it('should throw error if not awarded', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
      });
      expect(() => prize.markAsPaid()).toThrow(RacingDomainInvariantError);
    });

    it('should throw error if no driverId', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
        status: 'awarded',
      });
      expect(() => prize.markAsPaid()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('cancel', () => {
    it('should cancel pending prize', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
      });
      const cancelled = prize.cancel();
      expect(cancelled.status.toString()).toBe('cancelled');
    });

    it('should cancel awarded prize', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
        driverId: 'driver-1',
        status: 'awarded',
      });
      const cancelled = prize.cancel();
      expect(cancelled.status.toString()).toBe('cancelled');
    });

    it('should throw error if paid', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
        driverId: 'driver-1',
        status: 'paid',
      });
      expect(() => prize.cancel()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('isPending', () => {
    it('should return true for pending status', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
      });
      expect(prize.isPending()).toBe(true);
    });

    it('should return false for awarded status', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
        status: 'awarded',
      });
      expect(prize.isPending()).toBe(false);
    });
  });

  describe('isAwarded', () => {
    it('should return true for awarded status', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
        status: 'awarded',
      });
      expect(prize.isAwarded()).toBe(true);
    });

    it('should return false for pending status', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
      });
      expect(prize.isAwarded()).toBe(false);
    });
  });

  describe('isPaid', () => {
    it('should return true for paid status', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
        status: 'paid',
      });
      expect(prize.isPaid()).toBe(true);
    });

    it('should return false for awarded status', () => {
      const amount = Money.create(1000, 'USD');
      const prize = Prize.create({
        id: 'prize-1',
        seasonId: 'season-1',
        position: 1,
        amount,
        status: 'awarded',
      });
      expect(prize.isPaid()).toBe(false);
    });
  });
});