import { describe, it, expect, vi, Mocked } from 'vitest';
import { RaceService } from './RaceService';
import { RacesApiClient } from '../../api/races/RacesApiClient';
import { RaceDetailViewModel } from '../../view-models/RaceDetailViewModel';
import { RacesPageViewModel } from '../../view-models/RacesPageViewModel';
import { RaceStatsViewModel } from '../../view-models/RaceStatsViewModel';
import type { RaceDetailsViewModel } from '../../view-models/RaceDetailsViewModel';

describe('RaceService', () => {
  let mockApiClient: Mocked<RacesApiClient>;
  let service: RaceService;

  beforeEach(() => {
    mockApiClient = {
      getDetail: vi.fn(),
      getPageData: vi.fn(),
      getTotal: vi.fn(),
      register: vi.fn(),
      withdraw: vi.fn(),
      cancel: vi.fn(),
      complete: vi.fn(),
      reopen: vi.fn(),
    } as unknown as Mocked<RacesApiClient>;

    service = new RaceService(mockApiClient);
  });

  describe('getRaceDetail', () => {
    it('should call apiClient.getDetail and return RaceDetailViewModel', async () => {
      const raceId = 'race-123';
      const driverId = 'driver-456';

      const mockDto = {
        race: { id: raceId, track: 'Test Track' },
        league: { id: 'league-1', name: 'Test League' },
        entryList: [],
        registration: { isRegistered: true, canRegister: false },
        userResult: null,
      };

      mockApiClient.getDetail.mockResolvedValue(mockDto as any);

      const result = await service.getRaceDetail(raceId, driverId);

      expect(mockApiClient.getDetail).toHaveBeenCalledWith(raceId, driverId);
      expect(result).toBeInstanceOf(RaceDetailViewModel);
      expect(result.race?.id).toBe(raceId);
    });

    it('should throw error when apiClient.getDetail fails', async () => {
      const raceId = 'race-123';
      const driverId = 'driver-456';

      const error = new Error('API call failed');
      mockApiClient.getDetail.mockRejectedValue(error);

      await expect(service.getRaceDetail(raceId, driverId)).rejects.toThrow('API call failed');
    });
  });

  describe('getRaceDetails', () => {
    it('should call apiClient.getDetail and return a ViewModel-shaped object (no DTOs)', async () => {
      const raceId = 'race-123';
      const driverId = 'driver-456';

      const mockDto = {
        race: {
          id: raceId,
          track: 'Test Track',
          car: 'Test Car',
          scheduledAt: '2023-12-31T20:00:00Z',
          status: 'completed',
          sessionType: 'race',
        },
        league: { id: 'league-1', name: 'Test League', description: 'Desc', settings: { maxDrivers: 32 } },
        entryList: [],
        registration: { isUserRegistered: true, canRegister: false },
        userResult: null,
      };

      mockApiClient.getDetail.mockResolvedValue(mockDto as any);

      const result: RaceDetailsViewModel = await service.getRaceDetails(raceId, driverId);

      expect(mockApiClient.getDetail).toHaveBeenCalledWith(raceId, driverId);
      expect(result.race?.id).toBe(raceId);
      expect(result.league?.id).toBe('league-1');
      expect(result.registration.isUserRegistered).toBe(true);
      expect(result.canReopenRace).toBe(true);
    });
  });

  describe('getRacesPageData', () => {
    it('should call apiClient.getPageData and return RacesPageViewModel with transformed data', async () => {
      const mockDto = {
        races: [
          {
            id: 'race-1',
            track: 'Monza',
            car: 'Ferrari',
            scheduledAt: '2023-10-01T10:00:00Z',
            status: 'upcoming',
            leagueId: 'league-1',
            leagueName: 'Test League',
          },
          {
            id: 'race-2',
            track: 'Silverstone',
            car: 'Mercedes',
            scheduledAt: '2023-09-15T10:00:00Z',
            status: 'completed',
            leagueId: 'league-1',
            leagueName: 'Test League',
          },
        ],
      };

      mockApiClient.getPageData.mockResolvedValue(mockDto as any);

      const result = await service.getRacesPageData();

      expect(mockApiClient.getPageData).toHaveBeenCalled();
      expect(result).toBeInstanceOf(RacesPageViewModel);
      expect(result.upcomingRaces).toHaveLength(1);
      expect(result.completedRaces).toHaveLength(1);
      expect(result.totalCount).toBe(2);
      expect(result.upcomingRaces[0].title).toBe('Monza - Ferrari');
      expect(result.completedRaces[0].title).toBe('Silverstone - Mercedes');
    });

    it('should handle empty races array', async () => {
      const mockDto = { races: [] };

      mockApiClient.getPageData.mockResolvedValue(mockDto as any);

      const result = await service.getRacesPageData();

      expect(result.upcomingRaces).toHaveLength(0);
      expect(result.completedRaces).toHaveLength(0);
      expect(result.totalCount).toBe(0);
    });

    it('should throw error when apiClient.getPageData fails', async () => {
      const error = new Error('API call failed');
      mockApiClient.getPageData.mockRejectedValue(error);

      await expect(service.getRacesPageData()).rejects.toThrow('API call failed');
    });
  });

  describe('getRacesTotal', () => {
    it('should call apiClient.getTotal and return RaceStatsViewModel', async () => {
      const mockDto = { totalRaces: 42 };

      mockApiClient.getTotal.mockResolvedValue(mockDto as any);

      const result = await service.getRacesTotal();

      expect(mockApiClient.getTotal).toHaveBeenCalled();
      expect(result).toBeInstanceOf(RaceStatsViewModel);
      expect(result.totalRaces).toBe(42);
      expect(result.formattedTotalRaces).toBe('42');
    });

    it('should throw error when apiClient.getTotal fails', async () => {
      const error = new Error('API call failed');
      mockApiClient.getTotal.mockRejectedValue(error);

      await expect(service.getRacesTotal()).rejects.toThrow('API call failed');
    });
  });

  describe('reopenRace', () => {
    it('should call apiClient.reopen with raceId', async () => {
      const raceId = 'race-123';

      await service.reopenRace(raceId);

      expect(mockApiClient.reopen).toHaveBeenCalledWith(raceId);
    });

    it('should propagate errors from apiClient.reopen', async () => {
      const raceId = 'race-123';
      const error = new Error('API call failed');
      mockApiClient.reopen.mockRejectedValue(error);

      await expect(service.reopenRace(raceId)).rejects.toThrow('API call failed');
    });
  });
});