import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
  GetAllRacesUseCase,
  type GetAllRacesResult,
  type GetAllRacesInput,
} from './GetAllRacesUseCase';
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { Logger } from '@core/shared/application';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import { Race } from '../../domain/entities/Race';
import { League } from '../../domain/entities/League';

describe('GetAllRacesUseCase', () => {
  const mockRaceFindAll = vi.fn();
  const mockRaceRepo: IRaceRepository = {
    findById: vi.fn(),
    findAll: mockRaceFindAll,
    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(),
  };

  const mockLeagueFindAll = vi.fn();
  const mockLeagueRepo: ILeagueRepository = {
    findById: vi.fn(),
    findAll: mockLeagueFindAll,
    findByOwnerId: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
    exists: vi.fn(),
    searchByName: vi.fn(),
  };

  const mockLogger: Logger = {
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
  };

  let output: UseCaseOutputPort<GetAllRacesResult> & { present: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    vi.clearAllMocks();
    output = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<GetAllRacesResult> & { present: ReturnType<typeof vi.fn> };
  });

  it('should present domain races and leagues data', async () => {
    const useCase = new GetAllRacesUseCase(
      mockRaceRepo,
      mockLeagueRepo,
      mockLogger,
    );
    useCase.setOutput(output);

    const race1 = Race.create({
      id: 'race1',
      leagueId: 'league1',
      track: 'Track A',
      car: 'Car A',
      scheduledAt: new Date('2023-01-01T10:00:00Z'),
      status: 'scheduled',
    });

    const race2 = Race.create({
      id: 'race2',
      leagueId: 'league2',
      track: 'Track B',
      car: 'Car B',
      scheduledAt: new Date('2023-01-02T10:00:00Z'),
      status: 'scheduled',
    });

    const league1 = League.create({
      id: 'league1',
      name: 'League One',
      description: 'League One',
      ownerId: 'owner-1',
    });

    const league2 = League.create({
      id: 'league2',
      name: 'League Two',
      description: 'League Two',
      ownerId: 'owner-2',
    });

    mockRaceFindAll.mockResolvedValue([race1, race2]);
    mockLeagueFindAll.mockResolvedValue([league1, league2]);

    const input: GetAllRacesInput = {};

    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();
    expect(output.present).toHaveBeenCalledTimes(1);

    const presented = output.present.mock.calls[0]?.[0] as GetAllRacesResult;
    expect(presented.totalCount).toBe(2);
    expect(presented.races).toEqual([race1, race2]);
    expect(presented.leagues).toEqual([league1, league2]);
  });

  it('should present empty result when no races or leagues', async () => {
    const useCase = new GetAllRacesUseCase(
      mockRaceRepo,
      mockLeagueRepo,
      mockLogger,
    );
    useCase.setOutput(output);

    mockRaceFindAll.mockResolvedValue([]);
    mockLeagueFindAll.mockResolvedValue([]);

    const result = await useCase.execute({});

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();
    expect(output.present).toHaveBeenCalledTimes(1);

    const presented = output.present.mock.calls[0]?.[0] as GetAllRacesResult;
    expect(presented.totalCount).toBe(0);
    expect(presented.races).toEqual([]);
    expect(presented.leagues).toEqual([]);
  });

  it('should return error when repository throws and not present data', async () => {
    const useCase = new GetAllRacesUseCase(
      mockRaceRepo,
      mockLeagueRepo,
      mockLogger,
    );

    const error = new Error('Repository error');
    mockRaceFindAll.mockRejectedValue(error);

    const result = await useCase.execute({});

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details.message).toBe('Repository error');
    expect(output.present).not.toHaveBeenCalled();
  });
});
