import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { Driver } from '../../domain/entities/Driver';
import { League } from '../../domain/entities/League';
import { Protest } from '../../domain/entities/Protest';
import { Race } from '../../domain/entities/Race';
import { DriverRepository } from '../../domain/repositories/DriverRepository';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import {
    GetLeagueProtestsErrorCode,
    GetLeagueProtestsInput,
    GetLeagueProtestsUseCase,
} from './GetLeagueProtestsUseCase';

describe('GetLeagueProtestsUseCase', () => {
  let useCase: GetLeagueProtestsUseCase;
  let raceRepository: {
    findByLeagueId: Mock;
  };
  let protestRepository: {
    findByRaceId: Mock;
  };
  let driverRepository: {
    findById: Mock;
  };
  let leagueRepository: {
    findById: Mock;
  };
  beforeEach(() => {
    raceRepository = {
      findByLeagueId: vi.fn(),
    };
    protestRepository = {
      findByRaceId: vi.fn(),
    };
    driverRepository = {
      findById: vi.fn(),
    };
    leagueRepository = {
      findById: vi.fn(),
    };
    useCase = new GetLeagueProtestsUseCase(raceRepository as unknown as RaceRepository,
      protestRepository as unknown as ProtestRepository,
      driverRepository as unknown as DriverRepository,
      leagueRepository as unknown as LeagueRepository);
  });

  it('should return protests with races and drivers', async () => {
    const leagueId = 'league-1';
    const league = League.create({
      id: leagueId,
      name: 'Test League',
      ownerId: 'owner-1',
      description: 'A test league',
    });
    const race = Race.create({
      id: 'race-1',
      leagueId,
      scheduledAt: new Date(),
      track: 'Track 1',
      car: 'Car 1',
    });
    const protest = Protest.create({
      id: 'protest-1',
      raceId: 'race-1',
      protestingDriverId: 'driver-1',
      accusedDriverId: 'driver-2',
      incident: { lap: 1, description: 'Incident' },
      status: 'pending',
      filedAt: new Date(),
    });
    const driver1 = Driver.create({
      id: 'driver-1',
      iracingId: '123',
      name: 'Driver 1',
      country: 'US',
    });
    const driver2 = Driver.create({
      id: 'driver-2',
      iracingId: '456',
      name: 'Driver 2',
      country: 'UK',
    });

    leagueRepository.findById.mockResolvedValue(league);
    raceRepository.findByLeagueId.mockResolvedValue([race]);
    protestRepository.findByRaceId.mockResolvedValue([protest]);
    driverRepository.findById.mockImplementation((id: string) => {
      if (id === 'driver-1') return Promise.resolve(driver1);
      if (id === 'driver-2') return Promise.resolve(driver2);
      return Promise.resolve(null);
    });

    const input: GetLeagueProtestsInput = { leagueId };
    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    const resultValue = result.unwrap();
    expect(resultValue.league).toEqual(league);
    expect(resultValue.protests).toHaveLength(1);
    const presentedProtest = resultValue.protests[0];
    expect(presentedProtest.protest).toEqual(protest);
    expect(presentedProtest.race).toEqual(race);
    expect(presentedProtest.protestingDriver).toEqual(driver1);
    expect(presentedProtest.accusedDriver).toEqual(driver2);
  });

  it('should return empty protests when no races', async () => {
    const leagueId = 'league-1';
    const league = League.create({
      id: leagueId,
      name: 'Test League',
      ownerId: 'owner-1',
      description: 'A test league',
    });

    leagueRepository.findById.mockResolvedValue(league);
    raceRepository.findByLeagueId.mockResolvedValue([]);

    const input: GetLeagueProtestsInput = { leagueId };
    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    const resultValue = result.unwrap();
    expect(resultValue.league).toEqual(league);
    expect(resultValue.protests).toEqual([]);
  });

  it('should return LEAGUE_NOT_FOUND when league does not exist', async () => {
    const leagueId = 'missing-league';

    leagueRepository.findById.mockResolvedValue(null);

    const input: GetLeagueProtestsInput = { leagueId };
    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);

    const err = result.unwrapErr() as ApplicationErrorCode<
      GetLeagueProtestsErrorCode,
      { message: string }
    >;

    expect(err.code).toBe('LEAGUE_NOT_FOUND');
    expect(err.details.message).toBe('League not found');
    });

  it('should return REPOSITORY_ERROR when repository throws', async () => {
    const leagueId = 'league-1';
    const league = League.create({
      id: leagueId,
      name: 'Test League',
      ownerId: 'owner-1',
      description: 'A test league',
    });

    leagueRepository.findById.mockResolvedValue(league);
    raceRepository.findByLeagueId.mockRejectedValue(new Error('DB error'));

    const input: GetLeagueProtestsInput = { leagueId };
    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);

    const err = result.unwrapErr() as ApplicationErrorCode<
      GetLeagueProtestsErrorCode,
      { message: string }
    >;

    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details.message).toBe('DB error');
    });
});