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

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

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

  const createTestPenalty = (id: string, raceId: string, driverId: string, issuedBy: string) => {
    return Penalty.create({
      id,
      leagueId: 'league1',
      raceId,
      driverId,
      type: 'time_penalty',
      value: 5,
      reason: 'Test reason',
      issuedBy,
    });
  };

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

  describe('findById', () => {
    it('should return null if penalty not found', async () => {
      const result = await repository.findById('nonexistent');
      expect(result).toBeNull();
      expect(mockLogger.debug).toHaveBeenCalledWith('Finding penalty by id: nonexistent');
      expect(mockLogger.warn).toHaveBeenCalledWith('Penalty with id nonexistent not found.');
    });

    it('should return the penalty if found', async () => {
      const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
      await repository.create(penalty);

      const result = await repository.findById('1');
      expect(result).toEqual(penalty);
      expect(mockLogger.info).toHaveBeenCalledWith('Found penalty with id: 1.');
    });
  });

  describe('findByRaceId', () => {
    it('should return penalties filtered by race ID', async () => {
      const penalty1 = createTestPenalty('1', 'race1', 'driver1', 'steward1');
      const penalty2 = createTestPenalty('2', 'race2', 'driver2', 'steward1');
      await repository.create(penalty1);
      await repository.create(penalty2);

      const result = await repository.findByRaceId('race1');
      expect(result).toHaveLength(1);
      expect(result[0]).toEqual(penalty1);
    });
  });

  describe('findByDriverId', () => {
    it('should return penalties filtered by driver ID', async () => {
      const penalty1 = createTestPenalty('1', 'race1', 'driver1', 'steward1');
      const penalty2 = createTestPenalty('2', 'race1', 'driver2', 'steward1');
      await repository.create(penalty1);
      await repository.create(penalty2);

      const result = await repository.findByDriverId('driver1');
      expect(result).toHaveLength(1);
      expect(result[0]).toEqual(penalty1);
    });
  });

  describe('findByProtestId', () => {
    it('should return penalties filtered by protest ID', async () => {
      const penalty1 = Penalty.create({
        id: '1',
        leagueId: 'league1',
        raceId: 'race1',
        driverId: 'driver1',
        type: 'time_penalty',
        value: 5,
        reason: 'Test reason',
        protestId: 'protest1',
        issuedBy: 'steward1',
      });
      const penalty2 = createTestPenalty('2', 'race1', 'driver2', 'steward1');
      await repository.create(penalty1);
      await repository.create(penalty2);

      const result = await repository.findByProtestId('protest1');
      expect(result).toHaveLength(1);
      expect(result[0]).toEqual(penalty1);
    });
  });

  describe('findPending', () => {
    it('should return only pending penalties', async () => {
      const penalty1 = createTestPenalty('1', 'race1', 'driver1', 'steward1');
      const penalty2 = Penalty.create({
        id: '2',
        leagueId: 'league1',
        raceId: 'race1',
        driverId: 'driver2',
        type: 'time_penalty',
        value: 5,
        reason: 'Test reason',
        issuedBy: 'steward1',
        status: 'applied',
      });
      await repository.create(penalty1);
      await repository.create(penalty2);

      const result = await repository.findPending();
      expect(result).toHaveLength(1);
      expect(result[0]).toEqual(penalty1);
    });
  });

  describe('findIssuedBy', () => {
    it('should return penalties issued by a specific steward', async () => {
      const penalty1 = createTestPenalty('1', 'race1', 'driver1', 'steward1');
      const penalty2 = createTestPenalty('2', 'race1', 'driver2', 'steward2');
      await repository.create(penalty1);
      await repository.create(penalty2);

      const result = await repository.findIssuedBy('steward1');
      expect(result).toHaveLength(1);
      expect(result[0]).toEqual(penalty1);
    });
  });

  describe('create', () => {
    it('should create a new penalty', async () => {
      const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
      await repository.create(penalty);
      expect(mockLogger.info).toHaveBeenCalledWith('Penalty 1 created successfully.');
    });

    it('should throw error if penalty already exists', async () => {
      const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
      await repository.create(penalty);
      await expect(repository.create(penalty)).rejects.toThrow('Penalty with ID 1 already exists');
    });
  });

  describe('update', () => {
    it('should update an existing penalty', async () => {
      const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
      await repository.create(penalty);

      const updatedPenalty = penalty.markAsApplied();
      await repository.update(updatedPenalty);
      expect(mockLogger.info).toHaveBeenCalledWith('Penalty 1 updated successfully.');
    });

    it('should throw error if penalty does not exist', async () => {
      const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
      await expect(repository.update(penalty)).rejects.toThrow('Penalty with ID 1 not found');
    });
  });

  describe('exists', () => {
    it('should return true if penalty exists', async () => {
      const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
      await repository.create(penalty);

      const result = await repository.exists('1');
      expect(result).toBe(true);
    });

    it('should return false if penalty does not exist', async () => {
      const result = await repository.exists('nonexistent');
      expect(result).toBe(false);
    });
  });
});