import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Logger } from 'vite';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import {
    RegisterForRaceErrorCode,
    RegisterForRaceInput,
    RegisterForRaceResult,
    RegisterForRaceUseCase,
} from './RegisterForRaceUseCase';

describe('RegisterForRaceUseCase', () => {
  let useCase: RegisterForRaceUseCase;
  let registrationRepository: { isRegistered: Mock; register: Mock };
  let membershipRepository: { getMembership: Mock };
  let logger: { debug: Mock; warn: Mock; error: Mock; info: Mock };
  beforeEach(() => {
    registrationRepository = { isRegistered: vi.fn(), register: vi.fn() };
    membershipRepository = { getMembership: vi.fn() };
    logger = { debug: vi.fn(), warn: vi.fn(), error: vi.fn(), info: vi.fn() };
    useCase = new RegisterForRaceUseCase(registrationRepository as unknown as RaceRegistrationRepository,
      membershipRepository as unknown as LeagueMembershipRepository,
      logger as unknown as Logger);
  });

  const buildInput = (overrides: Partial<RegisterForRaceInput> = {}): RegisterForRaceInput => ({
    raceId: 'race-1',
    leagueId: 'league-1',
    driverId: 'driver-1',
    ...overrides,
  });

  const unwrapErr = (
    result: Result<
      void,
      ApplicationErrorCode<
        RegisterForRaceErrorCode,
        {
          message: string;
        }
      >
    >,
  ): ApplicationErrorCode<RegisterForRaceErrorCode, { message: string }> => result.unwrapErr();

  it('returns already registered error when driver is already registered', async () => {
    registrationRepository.isRegistered.mockResolvedValue(true);

    const result = await useCase.execute(buildInput());

    expect(result.isErr()).toBe(true);
    const error = unwrapErr(result);
    expect(error.code).toBe('ALREADY_REGISTERED');
    expect(error.details.message).toBe('Already registered for this race');
    });

  it('returns not active member error when membership is missing', async () => {
    registrationRepository.isRegistered.mockResolvedValue(false);
    membershipRepository.getMembership.mockResolvedValue(null);

    const result = await useCase.execute(buildInput());

    expect(result.isErr()).toBe(true);
    const error = unwrapErr(result);
    expect(error.code).toBe('NOT_ACTIVE_MEMBER');
    expect(error.details.message).toBe('Must be an active league member to register for races');
    });

  it('returns not active member error for inactive membership', async () => {
    registrationRepository.isRegistered.mockResolvedValue(false);
    membershipRepository.getMembership.mockResolvedValue({ status: 'inactive' });

    const result = await useCase.execute(buildInput());

    expect(result.isErr()).toBe(true);
    const error = unwrapErr(result);
    expect(error.code).toBe('NOT_ACTIVE_MEMBER');
    expect(error.details.message).toBe('Must be an active league member to register for races');
    });

  it('registers successfully and presents result', async () => {
    registrationRepository.isRegistered.mockResolvedValue(false);
    membershipRepository.getMembership.mockResolvedValue({ status: 'active' });
    registrationRepository.register.mockResolvedValue(undefined);

    const result = await useCase.execute(buildInput());

    expect(result.isOk()).toBe(true);
    const presented = result.unwrap();
    expect(presented).toEqual<RegisterForRaceResult>({
      raceId: 'race-1',
      driverId: 'driver-1',
      status: 'registered',
    });
    expect(registrationRepository.register).toHaveBeenCalled();
  });

  it('wraps unexpected repository errors', async () => {
    const error = new Error('db is down');
    registrationRepository.isRegistered.mockRejectedValue(error);

    const result = await useCase.execute(buildInput());

    expect(result.isErr()).toBe(true);
    const err = unwrapErr(result);
    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details.message).toBe('db is down');
    });
});