import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import { Race } from '../../domain/entities/Race';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import {
    GetRaceDetailUseCase,
    type GetRaceDetailErrorCode,
    type GetRaceDetailInput,
} from './GetRaceDetailUseCase';

describe('GetRaceDetailUseCase', () => {
  let useCase: GetRaceDetailUseCase;
  let raceRepository: { findById: Mock };
  let leagueRepository: { findById: Mock };
  let driverRepository: { findById: Mock };
  let raceRegistrationRepository: { findByRaceId: Mock };
  let resultRepository: { findByRaceId: Mock };
  let leagueMembershipRepository: { getMembership: Mock };

  beforeEach(() => {
    raceRepository = { findById: vi.fn() };
    leagueRepository = { findById: vi.fn() };
    driverRepository = { findById: vi.fn() };
    raceRegistrationRepository = { findByRaceId: vi.fn() };
    resultRepository = { findByRaceId: vi.fn() };
    leagueMembershipRepository = { getMembership: vi.fn() };

    useCase = new GetRaceDetailUseCase(
      raceRepository as unknown as RaceRepository,
      leagueRepository as unknown as LeagueRepository,
      driverRepository as unknown as DriverRepository,
      raceRegistrationRepository as unknown as RaceRegistrationRepository,
      resultRepository as unknown as ResultRepository,
      leagueMembershipRepository as unknown as LeagueMembershipRepository,
    );
  });

  it('should return race detail when race exists', async () => {
    const raceId = 'race-1';
    const driverId = 'driver-1';
    const race = Race.create({
      id: raceId,
      leagueId: 'league-1',
      track: 'Track 1',
      car: 'Car 1',
      scheduledAt: new Date('2099-01-01T10:00:00Z'),
      status: 'scheduled',
      strengthOfField: 1500,
      registeredCount: 10,
      maxParticipants: 20,
    });
    const league = {
      id: 'league-1',
      name: 'League 1',
      description: 'Description',
      settings: { maxDrivers: 20, qualifyingFormat: 'ladder' },
    };
    const registrations = [
      { driverId: { toString: () => 'driver-1' } },
      { driverId: { toString: () => 'driver-2' } },
    ];
    const membership = { status: { toString: () => 'active' } };
    const drivers = [
      { id: 'driver-1', name: 'Driver 1', country: 'US' },
      { id: 'driver-2', name: 'Driver 2', country: 'UK' },
    ];

    raceRepository.findById.mockResolvedValue(race);
    leagueRepository.findById.mockResolvedValue(league);
    raceRegistrationRepository.findByRaceId.mockResolvedValue(registrations);
    leagueMembershipRepository.getMembership.mockResolvedValue(membership);
    driverRepository.findById.mockImplementation((id: string) =>
      Promise.resolve(drivers.find(d => d.id === id) || null),
    );
    resultRepository.findByRaceId.mockResolvedValue([]);

    const input: GetRaceDetailInput = { raceId, driverId };
    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    const presented = result.unwrap();
    expect(presented.race).toEqual(race);
    expect(presented.league).toEqual(league);
    expect(presented.registrations).toEqual(registrations);
    expect(presented.drivers).toHaveLength(2);
    expect(presented.isUserRegistered).toBe(true);
    expect(presented.canRegister).toBe(true);
    expect(presented.userResult).toBeNull();
  });

  it('should return error when race not found', async () => {
    raceRepository.findById.mockResolvedValue(null);

    const input: GetRaceDetailInput = { raceId: 'race-1', driverId: 'driver-1' };
    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<GetRaceDetailErrorCode, { message: string }>;
    expect(err.code).toBe('RACE_NOT_FOUND');
    expect(err.details?.message).toBe('Race not found');
  });

  it('should include user result when race is completed', async () => {
    const raceId = 'race-1';
    const driverId = 'driver-1';
    const race = Race.create({
      id: raceId,
      leagueId: 'league-1',
      track: 'Track 1',
      car: 'Car 1',
      scheduledAt: new Date('2023-01-01T10:00:00Z'),
      status: 'completed',
    });
    const registrations: Array<{ driverId: { toString: () => string } }> = [];
    const userDomainResult = {
      driverId: { toString: () => driverId },
    } as unknown as { driverId: { toString: () => string } };

    raceRepository.findById.mockResolvedValue(race);
    leagueRepository.findById.mockResolvedValue(null);
    raceRegistrationRepository.findByRaceId.mockResolvedValue(registrations);
    leagueMembershipRepository.getMembership.mockResolvedValue(null);
    driverRepository.findById.mockResolvedValue(null);
    resultRepository.findByRaceId.mockResolvedValue([userDomainResult]);

    const input: GetRaceDetailInput = { raceId, driverId };
    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    const presented = result.unwrap();
    expect(presented.userResult).toBe(userDomainResult);
    expect(presented.race).toEqual(race);
    expect(presented.league).toBeNull();
    expect(presented.registrations).toEqual(registrations);
  });

  it('should wrap repository errors', async () => {
    const error = new Error('db down');
    raceRepository.findById.mockRejectedValue(error);

    const input: GetRaceDetailInput = { raceId: 'race-1', driverId: 'driver-1' };
    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<GetRaceDetailErrorCode, { message: string }>;
    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details.message).toBe('db down');
  });
});
