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

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

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

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

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

    it('should return the track if found', async () => {
      const track = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        shortName: 'MON',
        country: 'Italy',
        category: 'road',
        difficulty: 'advanced',
        lengthKm: 5.793,
        turns: 11,
        imageUrl: 'https://example.com/monza.jpg',
        gameId: 'iracing',
      });
      await repository.create(track);

      const result = await repository.findById('track1');
      expect(result).toEqual(track);
      expect(mockLogger.debug).toHaveBeenCalledWith('Finding track by id: track1');
      expect(mockLogger.info).toHaveBeenCalledWith('Found track: track1.');
    });
  });

  describe('findAll', () => {
    it('should return an empty array when no tracks', async () => {
      const result = await repository.findAll();
      expect(result).toEqual([]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Finding all tracks.');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 0 tracks.');
    });

    it('should return all tracks', async () => {
      const track1 = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      const track2 = Track.create({
        id: 'track2',
        name: 'Silverstone',
        country: 'UK',
        category: 'road',
        lengthKm: 5.891,
        turns: 18,
        gameId: 'iracing',
      });
      await repository.create(track1);
      await repository.create(track2);

      const result = await repository.findAll();
      expect(result).toEqual([track1, track2]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Finding all tracks.');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 2 tracks.');
    });
  });

  describe('findByGameId', () => {
    it('should return empty array if no tracks for game id', async () => {
      const result = await repository.findByGameId('iracing');
      expect(result).toEqual([]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by game id: iracing');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 0 tracks for game id: iracing.');
    });

    it('should return tracks for the game id', async () => {
      const track1 = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      const track2 = Track.create({
        id: 'track2',
        name: 'Silverstone',
        country: 'UK',
        category: 'road',
        lengthKm: 5.891,
        turns: 18,
        gameId: 'iracing',
      });
      const track3 = Track.create({
        id: 'track3',
        name: 'Daytona',
        country: 'USA',
        category: 'oval',
        lengthKm: 4.0,
        turns: 4,
        gameId: 'assetto',
      });
      await repository.create(track1);
      await repository.create(track2);
      await repository.create(track3);

      const result = await repository.findByGameId('iracing');
      expect(result).toEqual([track1, track2]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by game id: iracing');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 2 tracks for game id: iracing.');
    });
  });

  describe('findByCategory', () => {
    it('should return empty array if no tracks in category', async () => {
      const result = await repository.findByCategory('oval');
      expect(result).toEqual([]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by category: oval');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 0 tracks for category: oval.');
    });

    it('should return tracks in the category', async () => {
      const track1 = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      const track2 = Track.create({
        id: 'track2',
        name: 'Daytona',
        country: 'USA',
        category: 'oval',
        lengthKm: 4.0,
        turns: 4,
        gameId: 'iracing',
      });
      await repository.create(track1);
      await repository.create(track2);

      const result = await repository.findByCategory('road');
      expect(result).toEqual([track1]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by category: road');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 1 tracks for category: road.');
    });
  });

  describe('findByCountry', () => {
    it('should return empty array if no tracks in country', async () => {
      const result = await repository.findByCountry('France');
      expect(result).toEqual([]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by country: France');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 0 tracks for country: France.');
    });

    it('should return tracks in the country', async () => {
      const track1 = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      const track2 = Track.create({
        id: 'track2',
        name: 'Imola',
        country: 'Italy',
        category: 'road',
        lengthKm: 4.909,
        turns: 17,
        gameId: 'iracing',
      });
      const track3 = Track.create({
        id: 'track3',
        name: 'Silverstone',
        country: 'UK',
        category: 'road',
        lengthKm: 5.891,
        turns: 18,
        gameId: 'iracing',
      });
      await repository.create(track1);
      await repository.create(track2);
      await repository.create(track3);

      const result = await repository.findByCountry('Italy');
      expect(result).toEqual([track2, track1]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by country: Italy');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 2 tracks for country: Italy.');
    });
  });

  describe('searchByName', () => {
    it('should return empty array if no matches', async () => {
      const result = await repository.searchByName('nonexistent');
      expect(result).toEqual([]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Searching tracks by name query: nonexistent');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 0 tracks matching search query: nonexistent.');
    });

    it('should return tracks matching the query in name', async () => {
      const track1 = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        shortName: 'MON',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      const track2 = Track.create({
        id: 'track2',
        name: 'Silverstone Circuit',
        shortName: 'SIL',
        country: 'UK',
        category: 'road',
        lengthKm: 5.891,
        turns: 18,
        gameId: 'iracing',
      });
      await repository.create(track1);
      await repository.create(track2);

      const result = await repository.searchByName('Circuit');
      expect(result).toEqual([track1, track2]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Searching tracks by name query: Circuit');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 2 tracks matching search query: Circuit.');
    });

    it('should return tracks matching the query in short name', async () => {
      const track = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        shortName: 'MON',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      await repository.create(track);

      const result = await repository.searchByName('mon');
      expect(result).toEqual([track]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Searching tracks by name query: mon');
      expect(mockLogger.info).toHaveBeenCalledWith('Found 1 tracks matching search query: mon.');
    });
  });

  describe('create', () => {
    it('should create a new track', async () => {
      const track = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });

      const result = await repository.create(track);
      expect(result).toEqual(track);
      expect(mockLogger.debug).toHaveBeenCalledWith('Creating track: track1');
      expect(mockLogger.info).toHaveBeenCalledWith('Track track1 created successfully.');
    });

    it('should throw error if track already exists', async () => {
      const track = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      await repository.create(track);

      await expect(repository.create(track)).rejects.toThrow('Track with ID track1 already exists');
      expect(mockLogger.warn).toHaveBeenCalledWith('Track with ID track1 already exists.');
    });
  });

  describe('update', () => {
    it('should update an existing track', async () => {
      const track = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      await repository.create(track);

      const updatedTrack = Track.create({
        id: 'track1',
        name: 'Monza Grand Prix Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      const result = await repository.update(updatedTrack);
      expect(result).toEqual(updatedTrack);
      expect(mockLogger.debug).toHaveBeenCalledWith('Updating track: track1');
      expect(mockLogger.info).toHaveBeenCalledWith('Track track1 updated successfully.');
    });

    it('should throw error if track does not exist', async () => {
      const track = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });

      await expect(repository.update(track)).rejects.toThrow('Track with ID track1 not found');
      expect(mockLogger.warn).toHaveBeenCalledWith('Track with ID track1 not found for update.');
    });
  });

  describe('delete', () => {
    it('should delete an existing track', async () => {
      const track = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      await repository.create(track);

      await repository.delete('track1');
      expect(await repository.exists('track1')).toBe(false);
      expect(mockLogger.debug).toHaveBeenCalledWith('Deleting track: track1');
      expect(mockLogger.info).toHaveBeenCalledWith('Track track1 deleted successfully.');
    });

    it('should throw error if track does not exist', async () => {
      await expect(repository.delete('nonexistent')).rejects.toThrow('Track with ID nonexistent not found');
      expect(mockLogger.warn).toHaveBeenCalledWith('Track with ID nonexistent not found for deletion.');
    });
  });

  describe('exists', () => {
    it('should return true if track exists', async () => {
      const track = Track.create({
        id: 'track1',
        name: 'Monza Circuit',
        country: 'Italy',
        category: 'road',
        lengthKm: 5.793,
        turns: 11,
        gameId: 'iracing',
      });
      await repository.create(track);

      const result = await repository.exists('track1');
      expect(result).toBe(true);
      expect(mockLogger.debug).toHaveBeenCalledWith('Checking existence of track with id: track1');
    });

    it('should return false if track does not exist', async () => {
      const result = await repository.exists('nonexistent');
      expect(result).toBe(false);
      expect(mockLogger.debug).toHaveBeenCalledWith('Checking existence of track with id: nonexistent');
    });
  });
});