import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
  ImportRaceResultsApiUseCase,
  type ImportRaceResultsApiInput,
  type ImportRaceResultsApiResult,
  type ImportRaceResultsApiErrorCode,
} from './ImportRaceResultsApiUseCase';
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
import type { Logger } from '@core/shared/application';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Result } from '@core/shared/application/Result';

describe('ImportRaceResultsApiUseCase', () => {
  let useCase: ImportRaceResultsApiUseCase;
  let raceRepository: { findById: Mock };
  let leagueRepository: { findById: Mock };
  let resultRepository: { existsByRaceId: Mock; createMany: Mock };
  let driverRepository: { findByIRacingId: Mock };
  let standingRepository: { recalculate: Mock };
  let logger: { debug: Mock; info: Mock; warn: Mock; error: Mock };
  let output: UseCaseOutputPort<ImportRaceResultsApiResult> & { present: Mock };

  beforeEach(() => {
    raceRepository = { findById: vi.fn() };
    leagueRepository = { findById: vi.fn() };
    resultRepository = { existsByRaceId: vi.fn(), createMany: vi.fn() };
    driverRepository = { findByIRacingId: vi.fn() };
    standingRepository = { recalculate: vi.fn() };
    logger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    };
    output = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<ImportRaceResultsApiResult> & { present: Mock };

    useCase = new ImportRaceResultsApiUseCase(
      raceRepository as unknown as IRaceRepository,
      leagueRepository as unknown as ILeagueRepository,
      resultRepository as unknown as IResultRepository,
      driverRepository as unknown as IDriverRepository,
      standingRepository as unknown as IStandingRepository,
      logger as unknown as Logger,
      output,
    );
  });

  it('should return parse error for invalid JSON', async () => {
    const input: ImportRaceResultsApiInput = { raceId: 'race-1', resultsFileContent: 'invalid json' };

    const result: Result<
      void,
      ApplicationErrorCode<ImportRaceResultsApiErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      ImportRaceResultsApiErrorCode,
      { message: string }
    >;

    expect(err.code).toBe('PARSE_ERROR');
    expect(err.details?.message).toBe('Invalid JSON in results file content');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return race not found error', async () => {
    const input: ImportRaceResultsApiInput = { raceId: 'race-1', resultsFileContent: '[]' };

    raceRepository.findById.mockResolvedValue(null);

    const result: Result<
      void,
      ApplicationErrorCode<ImportRaceResultsApiErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      ImportRaceResultsApiErrorCode,
      { message: string }
    >;

    expect(err.code).toBe('RACE_NOT_FOUND');
    expect(err.details?.message).toBe('Race race-1 not found');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return league not found error', async () => {
    const input: ImportRaceResultsApiInput = { raceId: 'race-1', resultsFileContent: '[]' };

    raceRepository.findById.mockResolvedValue({ id: 'race-1', leagueId: 'league-1' });
    leagueRepository.findById.mockResolvedValue(null);

    const result: Result<
      void,
      ApplicationErrorCode<ImportRaceResultsApiErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      ImportRaceResultsApiErrorCode,
      { message: string }
    >;

    expect(err.code).toBe('LEAGUE_NOT_FOUND');
    expect(err.details?.message).toBe('League league-1 not found');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return results exist error', async () => {
    const input: ImportRaceResultsApiInput = { raceId: 'race-1', resultsFileContent: '[]' };

    raceRepository.findById.mockResolvedValue({ id: 'race-1', leagueId: 'league-1' });
    leagueRepository.findById.mockResolvedValue({ id: 'league-1' });
    resultRepository.existsByRaceId.mockResolvedValue(true);

    const result: Result<
      void,
      ApplicationErrorCode<ImportRaceResultsApiErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      ImportRaceResultsApiErrorCode,
      { message: string }
    >;

    expect(err.code).toBe('RESULTS_EXIST');
    expect(err.details?.message).toBe('Results already exist for this race');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return driver not found error', async () => {
    const input: ImportRaceResultsApiInput = {
      raceId: 'race-1',
      resultsFileContent:
        '[{"id":"result-1","raceId":"race-1","driverId":"123","position":1,"fastestLap":100,"incidents":0,"startPosition":1}]',
    };

    raceRepository.findById.mockResolvedValue({ id: 'race-1', leagueId: 'league-1' });
    leagueRepository.findById.mockResolvedValue({ id: 'league-1' });
    resultRepository.existsByRaceId.mockResolvedValue(false);
    driverRepository.findByIRacingId.mockResolvedValue(null);

    const result: Result<
      void,
      ApplicationErrorCode<ImportRaceResultsApiErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      ImportRaceResultsApiErrorCode,
      { message: string }
    >;

    expect(err.code).toBe('DRIVER_NOT_FOUND');
    expect(err.details?.message).toBe('Driver with iRacing ID 123 not found');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should import results successfully', async () => {
    const input: ImportRaceResultsApiInput = {
      raceId: 'race-1',
      resultsFileContent:
        '[{"id":"result-1","raceId":"race-1","driverId":"123","position":1,"fastestLap":100,"incidents":0,"startPosition":1}]',
    };

    raceRepository.findById.mockResolvedValue({ id: 'race-1', leagueId: 'league-1' });
    leagueRepository.findById.mockResolvedValue({ id: 'league-1' });
    resultRepository.existsByRaceId.mockResolvedValue(false);
    driverRepository.findByIRacingId.mockResolvedValue({ id: 'driver-1' });
    resultRepository.createMany.mockResolvedValue(undefined);
    standingRepository.recalculate.mockResolvedValue(undefined);

    const result: Result<
      void,
      ApplicationErrorCode<ImportRaceResultsApiErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();

    expect(output.present).toHaveBeenCalledTimes(1);
    const presentedRaw = output.present.mock.calls[0]?.[0];
    expect(presentedRaw).toBeDefined();
    const presented = presentedRaw as ImportRaceResultsApiResult;

    expect(presented.success).toBe(true);
    expect(presented.raceId).toBe('race-1');
    expect(presented.leagueId).toBe('league-1');
    expect(presented.driversProcessed).toBe(1);
    expect(presented.resultsRecorded).toBe(1);
    expect(presented.errors).toEqual([]);

    expect(resultRepository.createMany).toHaveBeenCalledTimes(1);
    const createdManyArg = resultRepository.createMany.mock.calls[0]?.[0] as unknown[];
    expect(createdManyArg).toHaveLength(1);
    const created = createdManyArg[0] as unknown as {
      id: string;
      raceId: { toString(): string };
      driverId: { toString(): string };
      position: { toNumber(): number };
      fastestLap: { toNumber(): number };
      incidents: { toNumber(): number };
      startPosition: { toNumber(): number };
    };

    expect(created.id).toBe('result-1');
    expect(created.raceId.toString()).toBe('race-1');
    expect(created.driverId.toString()).toBe('driver-1');
    expect(created.position.toNumber()).toBe(1);
    expect(created.fastestLap.toNumber()).toBe(100);
    expect(created.incidents.toNumber()).toBe(0);
    expect(created.startPosition.toNumber()).toBe(1);
    expect(standingRepository.recalculate).toHaveBeenCalledWith('league-1');
  });
});