import { vi, describe, it, expect, beforeEach } from 'vitest';
import { InMemoryRaceRegistrationRepository } from './InMemoryRaceRegistrationRepository';
import { RaceRegistration } from '@core/racing/domain/entities/RaceRegistration';
import type { Logger } from '@core/shared/application';

describe('InMemoryRaceRegistrationRepository', () => {
  let repository: InMemoryRaceRegistrationRepository;
  let mockLogger: Logger;

  const createTestRegistration = (raceId: string, driverId: string, id?: string) => {
    const props: { raceId: string; driverId: string; id?: string } = {
      raceId,
      driverId,
    };
    if (id) {
      props.id = id;
    }
    return RaceRegistration.create(props);
  };

  beforeEach(() => {
    mockLogger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    };
    repository = new InMemoryRaceRegistrationRepository(mockLogger);
  });

  describe('constructor', () => {
    it('should initialize with a logger', () => {
      expect(repository).toBeDefined();
      expect(mockLogger.info).toHaveBeenCalledWith('InMemoryRaceRegistrationRepository initialized.');
    });
  });

  describe('isRegistered', () => {
    it('should return false if driver is not registered for race', async () => {
      const result = await repository.isRegistered('race1', 'driver1');
      expect(result).toBe(false);
      expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Checking if driver driver1 is registered for race race1.');
    });

    it('should return true if driver is registered for race', async () => {
      const registration = createTestRegistration('race1', 'driver1');
      await repository.register(registration);

      const result = await repository.isRegistered('race1', 'driver1');
      expect(result).toBe(true);
    });
  });

  describe('getRegisteredDrivers', () => {
    it('should return empty array if no drivers registered for race', async () => {
      const result = await repository.getRegisteredDrivers('race1');
      expect(result).toEqual([]);
      expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Getting registered drivers for race race1.');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 0 registered drivers for race race1.');
    });

    it('should return registered drivers for race', async () => {
      const registration1 = createTestRegistration('race1', 'driver1');
      const registration2 = createTestRegistration('race1', 'driver2');
      const registration3 = createTestRegistration('race2', 'driver1');
      await repository.register(registration1);
      await repository.register(registration2);
      await repository.register(registration3);

      const result = await repository.getRegisteredDrivers('race1');
      expect(result).toHaveLength(2);
      expect(result).toContain('driver1');
      expect(result).toContain('driver2');
    });
  });

  describe('getRegistrationCount', () => {
    it('should return 0 if no registrations for race', async () => {
      const result = await repository.getRegistrationCount('race1');
      expect(result).toBe(0);
      expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Getting registration count for race race1.');
      expect(mockLogger.info).toHaveBeenCalledWith('Registration count for race race1: 0.');
    });

    it('should return correct count of registrations for race', async () => {
      const registration1 = createTestRegistration('race1', 'driver1');
      const registration2 = createTestRegistration('race1', 'driver2');
      await repository.register(registration1);
      await repository.register(registration2);

      const result = await repository.getRegistrationCount('race1');
      expect(result).toBe(2);
    });
  });

  describe('register', () => {
    it('should register a driver for a race', async () => {
      const registration = createTestRegistration('race1', 'driver1');

      await repository.register(registration);

      const isRegistered = await repository.isRegistered('race1', 'driver1');
      expect(isRegistered).toBe(true);
      expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Registering driver driver1 for race race1.');
      expect(mockLogger.info).toHaveBeenCalledWith('Driver driver1 registered for race race1.');
    });

    it('should throw error if driver already registered', async () => {
      const registration = createTestRegistration('race1', 'driver1');
      await repository.register(registration);

      await expect(repository.register(registration)).rejects.toThrow('Driver already registered for this race');
      expect(mockLogger.warn).toHaveBeenCalledWith('Driver driver1 already registered for race race1.');
    });
  });

  describe('withdraw', () => {
    it('should withdraw a driver from a race', async () => {
      const registration = createTestRegistration('race1', 'driver1');
      await repository.register(registration);

      await repository.withdraw('race1', 'driver1');

      const isRegistered = await repository.isRegistered('race1', 'driver1');
      expect(isRegistered).toBe(false);
      expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Withdrawing driver driver1 from race race1.');
      expect(mockLogger.info).toHaveBeenCalledWith('Driver driver1 withdrawn from race race1.');
    });

    it('should throw error if driver not registered', async () => {
      await expect(repository.withdraw('race1', 'driver1')).rejects.toThrow('Driver not registered for this race');
      expect(mockLogger.warn).toHaveBeenCalledWith('Driver driver1 not registered for race race1. No withdrawal needed.');
    });
  });

  describe('getDriverRegistrations', () => {
    it('should return empty array if driver has no registrations', async () => {
      const result = await repository.getDriverRegistrations('driver1');
      expect(result).toEqual([]);
      expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Getting registrations for driver: driver1.');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 0 registrations for driver driver1.');
    });

    it('should return race IDs for driver registrations', async () => {
      const registration1 = createTestRegistration('race1', 'driver1');
      const registration2 = createTestRegistration('race2', 'driver1');
      const registration3 = createTestRegistration('race1', 'driver2');
      await repository.register(registration1);
      await repository.register(registration2);
      await repository.register(registration3);

      const result = await repository.getDriverRegistrations('driver1');
      expect(result).toHaveLength(2);
      expect(result).toContain('race1');
      expect(result).toContain('race2');
    });
  });

  describe('clearRaceRegistrations', () => {
    it('should clear all registrations for a race', async () => {
      const registration1 = createTestRegistration('race1', 'driver1');
      const registration2 = createTestRegistration('race1', 'driver2');
      const registration3 = createTestRegistration('race2', 'driver1');
      await repository.register(registration1);
      await repository.register(registration2);
      await repository.register(registration3);

      await repository.clearRaceRegistrations('race1');

      const count = await repository.getRegistrationCount('race1');
      expect(count).toBe(0);
      const race2Count = await repository.getRegistrationCount('race2');
      expect(race2Count).toBe(1);
      expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Clearing all registrations for race: race1.');
      expect(mockLogger.info).toHaveBeenCalledWith('Cleared 2 registrations for race race1.');
    });
  });
});