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

/* eslint-disable @typescript-eslint/no-explicit-any */

describe('InMemoryStandingRepository', () => {
  let repository: InMemoryStandingRepository;
  let mockLogger: Logger;
  let mockResultRepository: any;
  let mockRaceRepository: any;
  let mockLeagueRepository: any;
  const testPointsSystems: Record<string, Record<number, number>> = {
    'f1-2024': {
      1: 25, 2: 18, 3: 15, 4: 12, 5: 10,
      6: 8, 7: 6, 8: 4, 9: 2, 10: 1
    },
    'indycar': {
      1: 50, 2: 40, 3: 35, 4: 32, 5: 30,
      6: 28, 7: 26, 8: 24, 9: 22, 10: 20,
      11: 19, 12: 18, 13: 17, 14: 16, 15: 15
    }
  };

  beforeEach(() => {
    mockLogger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    };
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    mockResultRepository = {
      findById: vi.fn(),
      findAll: vi.fn(),
      findByRaceId: vi.fn(),
      findByDriverId: vi.fn(),
      findByDriverIdAndLeagueId: vi.fn(),
      create: vi.fn(),
      createMany: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
      deleteByRaceId: vi.fn(),
      exists: vi.fn(),
      existsByRaceId: vi.fn(),
    } as any;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    mockRaceRepository = {
      findById: vi.fn(),
      findAll: vi.fn(),
      findByLeagueId: vi.fn(),
      findUpcomingByLeagueId: vi.fn(),
      findCompletedByLeagueId: vi.fn(),
      findByStatus: vi.fn(),
      findByDateRange: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
      exists: vi.fn(),
    } as any;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    mockLeagueRepository = {
      findById: vi.fn(),
      findByOwnerId: vi.fn(),
      searchByName: vi.fn(),
      findAll: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
      exists: vi.fn(),
    } as any;
    repository = new InMemoryStandingRepository(
      mockLogger,
      testPointsSystems,
      mockResultRepository,
      mockRaceRepository,
      mockLeagueRepository
    );
  });

  const createTestStanding = (leagueId: string, driverId: string, position: number = 1, points: number = 0) => {
    return Standing.create({
      leagueId,
      driverId,
      position,
      points,
    });
  };

  describe('constructor', () => {
    it('should initialize with required parameters', () => {
      expect(repository).toBeDefined();
      expect(mockLogger.info).toHaveBeenCalledWith('InMemoryStandingRepository initialized.');
    });
  });

  describe('findByLeagueId', () => {
    it('should return standings for the specified league', async () => {
      const standing1 = createTestStanding('league1', 'driver1', 1, 25);
      const standing2 = createTestStanding('league1', 'driver2', 2, 18);
      const standing3 = createTestStanding('league2', 'driver3', 1, 50);
      await repository.save(standing1);
      await repository.save(standing2);
      await repository.save(standing3);

      const result = await repository.findByLeagueId('league1');
      expect(result).toHaveLength(2);
      expect(result[0]).toEqual(standing1);
      expect(result[1]).toEqual(standing2);
    });

    it('should return empty array if no standings for league', async () => {
      const result = await repository.findByLeagueId('nonexistent');
      expect(result).toEqual([]);
    });

    it('should sort standings by position then points', async () => {
      const standing1 = createTestStanding('league1', 'driver1', 2, 18);
      const standing2 = createTestStanding('league1', 'driver2', 1, 25);
      await repository.save(standing1);
      await repository.save(standing2);

      const result = await repository.findByLeagueId('league1');
      expect(result[0]).toEqual(standing2); // position 1
      expect(result[1]).toEqual(standing1); // position 2
    });
  });

  describe('findByDriverIdAndLeagueId', () => {
    it('should return the standing if found', async () => {
      const standing = createTestStanding('league1', 'driver1');
      await repository.save(standing);

      const result = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
      expect(result).toEqual(standing);
    });

    it('should return null if not found', async () => {
      const result = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
      expect(result).toBeNull();
    });
  });

  describe('findAll', () => {
    it('should return all standings', async () => {
      const standing1 = createTestStanding('league1', 'driver1');
      const standing2 = createTestStanding('league2', 'driver2');
      await repository.save(standing1);
      await repository.save(standing2);

      const result = await repository.findAll();
      expect(result).toHaveLength(2);
      expect(result).toContain(standing1);
      expect(result).toContain(standing2);
    });
  });

  describe('save', () => {
    it('should save a new standing', async () => {
      const standing = createTestStanding('league1', 'driver1');
      const result = await repository.save(standing);
      expect(result).toEqual(standing);
      expect(mockLogger.info).toHaveBeenCalledWith('Standing for league league1, driver driver1 saved successfully.');
    });

    it('should update existing standing', async () => {
      const standing = createTestStanding('league1', 'driver1', 1, 25);
      await repository.save(standing);
      const updatedStanding = createTestStanding('league1', 'driver1', 1, 35);
      const result = await repository.save(updatedStanding);
      expect(result).toEqual(updatedStanding);
    });
  });

  describe('saveMany', () => {
    it('should save multiple standings', async () => {
      const standings = [
        createTestStanding('league1', 'driver1'),
        createTestStanding('league1', 'driver2'),
      ];
      const result = await repository.saveMany(standings);
      expect(result).toEqual(standings);
      expect(mockLogger.info).toHaveBeenCalledWith('2 standings saved successfully.');
    });
  });

  describe('delete', () => {
    it('should delete existing standing', async () => {
      const standing = createTestStanding('league1', 'driver1');
      await repository.save(standing);
      await repository.delete('league1', 'driver1');
      const found = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
      expect(found).toBeNull();
      expect(mockLogger.info).toHaveBeenCalledWith('Standing for league league1, driver driver1 deleted successfully.');
    });

    it('should log warning if standing not found', async () => {
      await repository.delete('league1', 'driver1');
      expect(mockLogger.warn).toHaveBeenCalledWith('Standing for league league1, driver driver1 not found for deletion.');
    });
  });

  describe('deleteByLeagueId', () => {
    it('should delete all standings for a league', async () => {
      const standing1 = createTestStanding('league1', 'driver1');
      const standing2 = createTestStanding('league1', 'driver2');
      const standing3 = createTestStanding('league2', 'driver3');
      await repository.save(standing1);
      await repository.save(standing2);
      await repository.save(standing3);

      await repository.deleteByLeagueId('league1');
      const league1Standings = await repository.findByLeagueId('league1');
      const league2Standings = await repository.findByLeagueId('league2');
      expect(league1Standings).toHaveLength(0);
      expect(league2Standings).toHaveLength(1);
      expect(mockLogger.info).toHaveBeenCalledWith('Deleted 2 standings for league id: league1.');
    });
  });

  describe('exists', () => {
    it('should return true if standing exists', async () => {
      const standing = createTestStanding('league1', 'driver1');
      await repository.save(standing);
      const result = await repository.exists('league1', 'driver1');
      expect(result).toBe(true);
    });

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

  describe('recalculate', () => {
    it('should throw error if repositories are missing', async () => {
      const repoWithoutDeps = new InMemoryStandingRepository(mockLogger, testPointsSystems);
      await expect(repoWithoutDeps.recalculate('league1')).rejects.toThrow('Cannot recalculate standings: missing required repositories');
    });

    it('should throw error if league not found', async () => {
      mockLeagueRepository.findById.mockResolvedValue(null);
      await expect(repository.recalculate('nonexistent')).rejects.toThrow('League with ID nonexistent not found');
    });

    it('should return empty array if no completed races', async () => {
      const mockLeague = {
        id: 'league1',
        settings: { pointsSystem: 'f1-2024', customPoints: null },
      };
      mockLeagueRepository.findById.mockResolvedValue(mockLeague);
      mockRaceRepository.findCompletedByLeagueId.mockResolvedValue([]);
      const result = await repository.recalculate('league1');
      expect(result).toEqual([]);
    });

    it('should recalculate standings based on race results', async () => {
      const mockLeague = {
        id: 'league1',
        settings: { pointsSystem: 'f1-2024', customPoints: null },
      };
      const mockRace = { id: 'race1' };
      const mockResult1 = { driverId: 'driver1', position: 1 };
      const mockResult2 = { driverId: 'driver2', position: 2 };

      mockLeagueRepository.findById.mockResolvedValue(mockLeague);
      mockRaceRepository.findCompletedByLeagueId.mockResolvedValue([mockRace]);
      mockResultRepository.findByRaceId.mockResolvedValue([mockResult1, mockResult2]);

      const result = await repository.recalculate('league1');
      expect(result).toHaveLength(2);
      expect(result[0]?.driverId.toString()).toBe('driver1');
      expect(result[0]?.points.toNumber()).toBe(25); // 1st place in f1-2024
      expect(result[1]?.driverId.toString()).toBe('driver2');
      expect(result[1]?.points.toNumber()).toBe(18); // 2nd place
    });

    it('should use custom points if available', async () => {
      const customPoints = { 1: 100, 2: 80 };
      const mockLeague = {
        id: 'league1',
        settings: { pointsSystem: 'f1-2024', customPoints },
      };
      const mockRace = { id: 'race1' };
      const mockResult = { driverId: 'driver1', position: 1 };

      mockLeagueRepository.findById.mockResolvedValue(mockLeague);
      mockRaceRepository.findCompletedByLeagueId.mockResolvedValue([mockRace]);
      mockResultRepository.findByRaceId.mockResolvedValue([mockResult]);

      const result = await repository.recalculate('league1');
      expect(result[0]?.points.toNumber()).toBe(100);
    });
  });
});