import { describe, it, expect, vi } from 'vitest';
import {
  GetDriversLeaderboardUseCase,
  type GetDriversLeaderboardInput,
} from './GetDriversLeaderboardUseCase';
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
import type { IRankingUseCase } from './IRankingUseCase';
import type { IDriverStatsUseCase } from './IDriverStatsUseCase';
import type { Logger } from '@core/shared/application';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { GetDriversLeaderboardResult } from './GetDriversLeaderboardUseCase';

describe('GetDriversLeaderboardUseCase', () => {
  const mockDriverFindAll = vi.fn();
  const mockDriverRepo: IDriverRepository = {
    findById: vi.fn(),
    findByIRacingId: vi.fn(),
    existsByIRacingId: vi.fn(),
    findAll: mockDriverFindAll,
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
    exists: vi.fn(),
  };

  const mockRankingGetAllDriverRankings = vi.fn();
  const mockRankingUseCase: IRankingUseCase = {
    getAllDriverRankings: mockRankingGetAllDriverRankings,
  };

  const mockDriverStatsGetDriverStats = vi.fn();
  const mockDriverStatsUseCase: IDriverStatsUseCase = {
    getDriverStats: mockDriverStatsGetDriverStats,
  };

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

  const mockOutput: UseCaseOutputPort<GetDriversLeaderboardResult> = {
    present: vi.fn(),
  };

  it('should return drivers leaderboard data', async () => {
    const useCase = new GetDriversLeaderboardUseCase(
      mockDriverRepo,
      mockRankingUseCase,
      mockDriverStatsUseCase,
      mockLogger,
      mockOutput,
    );

    const driver1 = {
      id: 'driver1',
      name: { value: 'Driver One' },
      country: { value: 'US' },
      avatarRef: { type: 'system-default', variant: 'avatar' }
    };
    const driver2 = {
      id: 'driver2',
      name: { value: 'Driver Two' },
      country: { value: 'US' },
      avatarRef: { type: 'system-default', variant: 'avatar' }
    };
    const rankings = [
      { driverId: 'driver1', rating: 2500, overallRank: 1 },
      { driverId: 'driver2', rating: 2400, overallRank: 2 },
    ];
    const stats1 = { totalRaces: 10, wins: 5, podiums: 7 };
    const stats2 = { totalRaces: 8, wins: 3, podiums: 4 };

    mockDriverFindAll.mockResolvedValue([driver1, driver2]);
    mockRankingGetAllDriverRankings.mockReturnValue(rankings);
    mockDriverStatsGetDriverStats.mockImplementation((id: string) => {
      if (id === 'driver1') return stats1;
      if (id === 'driver2') return stats2;
      return null;
    });

    const input: GetDriversLeaderboardInput = { leagueId: 'league-1' };

    const result = await useCase.execute(input);

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

    expect(mockOutput.present).toHaveBeenCalledWith({
      items: [
        expect.objectContaining({
          driver: driver1,
          rating: 2500,
          skillLevel: 'advanced',
          racesCompleted: 10,
          wins: 5,
          podiums: 7,
          isActive: true,
          rank: 1,
          avatarRef: driver1.avatarRef,
        }),
        expect.objectContaining({
          driver: driver2,
          rating: 2400,
          skillLevel: 'intermediate',
          racesCompleted: 8,
          wins: 3,
          podiums: 4,
          isActive: true,
          rank: 2,
          avatarRef: driver2.avatarRef,
        }),
      ],
      totalRaces: 18,
      totalWins: 8,
      activeCount: 2,
    });
  });

  it('should return empty result when no drivers', async () => {
    const useCase = new GetDriversLeaderboardUseCase(
      mockDriverRepo,
      mockRankingUseCase,
      mockDriverStatsUseCase,
      mockLogger,
      mockOutput,
    );

    mockDriverFindAll.mockResolvedValue([]);
    mockRankingGetAllDriverRankings.mockReturnValue([]);

    const input: GetDriversLeaderboardInput = { leagueId: 'league-1' };

    const result = await useCase.execute(input);

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

    expect(mockOutput.present).toHaveBeenCalledWith({
      items: [],
      totalRaces: 0,
      totalWins: 0,
      activeCount: 0,
    });
  });

  it('should handle drivers without stats', async () => {
    const useCase = new GetDriversLeaderboardUseCase(
      mockDriverRepo,
      mockRankingUseCase,
      mockDriverStatsUseCase,
      mockLogger,
      mockOutput,
    );

    const driver1 = {
      id: 'driver1',
      name: { value: 'Driver One' },
      country: { value: 'US' },
      avatarRef: { type: 'system-default', variant: 'avatar' }
    };
    const rankings = [{ driverId: 'driver1', rating: 2500, overallRank: 1 }];

    mockDriverFindAll.mockResolvedValue([driver1]);
    mockRankingGetAllDriverRankings.mockReturnValue(rankings);
    mockDriverStatsGetDriverStats.mockReturnValue(null);

    const input: GetDriversLeaderboardInput = { leagueId: 'league-1' };

    const result = await useCase.execute(input);

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

    expect(mockOutput.present).toHaveBeenCalledWith({
      items: [
        expect.objectContaining({
          driver: driver1,
          rating: 2500,
          skillLevel: 'advanced',
          racesCompleted: 0,
          wins: 0,
          podiums: 0,
          isActive: false,
          rank: 1,
          avatarRef: driver1.avatarRef,
        }),
      ],
      totalRaces: 0,
      totalWins: 0,
      activeCount: 0,
    });
  });

  it('should return error when repository throws', async () => {
    const useCase = new GetDriversLeaderboardUseCase(
      mockDriverRepo,
      mockRankingUseCase,
      mockDriverStatsUseCase,
      mockLogger,
      mockOutput,
    );

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

    const input: GetDriversLeaderboardInput = { leagueId: 'league-1' };

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('REPOSITORY_ERROR');
    if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
      expect(err.details.message).toBe('Repository error');
    }
  });
});
