import { describe, it, expect, vi, Mocked } from 'vitest';
import { ProtestService } from './ProtestService';
import { ProtestsApiClient } from '../../api/protests/ProtestsApiClient';
import { ProtestViewModel } from '../../view-models/ProtestViewModel';
import { RaceViewModel } from '../../view-models/RaceViewModel';
import { ProtestDriverViewModel } from '../../view-models/ProtestDriverViewModel';

describe('ProtestService', () => {
  let mockApiClient: Mocked<ProtestsApiClient>;
  let service: ProtestService;

  beforeEach(() => {
    mockApiClient = {
      getLeagueProtests: vi.fn(),
      getLeagueProtest: vi.fn(),
      applyPenalty: vi.fn(),
      requestDefense: vi.fn(),
      reviewProtest: vi.fn(),
      getRaceProtests: vi.fn(),
    } as unknown as Mocked<ProtestsApiClient>;

    service = new ProtestService(mockApiClient);
  });

  describe('getLeagueProtests', () => {
    it('should call apiClient.getLeagueProtests and return transformed data', async () => {
      const leagueId = 'league-123';
      const mockDto = {
        protests: [
          {
            id: 'protest-1',
            protestingDriverId: 'driver-1',
            accusedDriverId: 'driver-2',
            incident: { lap: 5, description: 'Contact at turn 1' },
            filedAt: '2023-10-01T10:00:00Z',
            status: 'pending',
          },
        ],
        racesById: { 'race-1': { id: 'race-1', track: 'Monza' } },
        driversById: {
          'driver-1': { id: 'driver-1', name: 'Driver 1' },
          'driver-2': { id: 'driver-2', name: 'Driver 2' },
        },
      };

      mockApiClient.getLeagueProtests.mockResolvedValue(mockDto as any);

      const result = await service.getLeagueProtests(leagueId);

      expect(mockApiClient.getLeagueProtests).toHaveBeenCalledWith(leagueId);
      expect(result.protests).toHaveLength(1);
      expect(result.protests[0]).toBeInstanceOf(ProtestViewModel);
      expect(result.racesById).toEqual(mockDto.racesById);
      expect(result.driversById).toEqual(mockDto.driversById);
    });

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

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

  describe('getProtestById', () => {
    it('should call apiClient.getLeagueProtest and return transformed data', async () => {
      const leagueId = 'league-123';
      const protestId = 'protest-1';
      const mockDto = {
        protests: [
          {
            id: protestId,
            protestingDriverId: 'driver-1',
            accusedDriverId: 'driver-2',
            incident: { lap: 5, description: 'Contact at turn 1' },
            filedAt: '2023-10-01T10:00:00Z',
            status: 'pending',
          },
        ],
        racesById: { 'race-1': { id: 'race-1', track: 'Monza', scheduledAt: '2023-10-01T10:00:00Z' } },
        driversById: {
          'driver-1': { id: 'driver-1', name: 'Driver 1', avatarUrl: 'https://example.com/avatar1.jpg' },
          'driver-2': { id: 'driver-2', name: 'Driver 2', avatarUrl: 'https://example.com/avatar2.jpg' },
        },
      };

      mockApiClient.getLeagueProtest.mockResolvedValue(mockDto as any);

      const result = await service.getProtestById(leagueId, protestId);

      expect(mockApiClient.getLeagueProtest).toHaveBeenCalledWith(leagueId, protestId);
      expect(result).not.toBeNull();
      expect(result?.protest).toBeInstanceOf(ProtestViewModel);
      expect(result?.race).toBeInstanceOf(RaceViewModel);
      expect(result?.protestingDriver).toBeInstanceOf(ProtestDriverViewModel);
      expect(result?.accusedDriver).toBeInstanceOf(ProtestDriverViewModel);
    });

    it('should return null when protest not found', async () => {
      const leagueId = 'league-123';
      const protestId = 'protest-999';
      const mockDto = {
        protests: [],
        racesById: {},
        driversById: {},
      };

      mockApiClient.getLeagueProtest.mockResolvedValue(mockDto);

      const result = await service.getProtestById(leagueId, protestId);

      expect(result).toBeNull();
    });

    it('should throw error when apiClient.getLeagueProtest fails', async () => {
      const leagueId = 'league-123';
      const protestId = 'protest-1';
      const error = new Error('API call failed');
      mockApiClient.getLeagueProtest.mockRejectedValue(error);

      await expect(service.getProtestById(leagueId, protestId)).rejects.toThrow('API call failed');
    });
  });

  describe('applyPenalty', () => {
    it('should call apiClient.applyPenalty', async () => {
      const input = {
        protestId: 'protest-123',
        driverId: 'driver-456',
        type: 'time',
        value: 10,
        reason: 'Test reason',
      };

      mockApiClient.applyPenalty.mockResolvedValue(undefined);

      await service.applyPenalty(input as any);

      expect(mockApiClient.applyPenalty).toHaveBeenCalledWith(input);
    });
  });

  describe('requestDefense', () => {
    it('should call apiClient.requestDefense', async () => {
      const input = {
        protestId: 'protest-123',
        driverId: 'driver-456',
        message: 'Please provide defense',
      };

      mockApiClient.requestDefense.mockResolvedValue(undefined);

      await service.requestDefense(input as any);

      expect(mockApiClient.requestDefense).toHaveBeenCalledWith(input);
    });
  });

  describe('reviewProtest', () => {
    it('should call apiClient.reviewProtest', async () => {
      const input = {
        protestId: 'protest-123',
        stewardId: 'steward-456',
        decision: 'uphold',
        decisionNotes: 'Test notes',
      };

      mockApiClient.reviewProtest.mockResolvedValue(undefined);

      await service.reviewProtest(input);

      expect(mockApiClient.reviewProtest).toHaveBeenCalledWith(input);
    });
  });

  describe('findByRaceId', () => {
    it('should call apiClient.getRaceProtests and return protests array', async () => {
      const raceId = 'race-123';
      const mockDto = {
        protests: [
          { id: 'protest-1', protestingDriverId: 'driver-1', accusedDriverId: 'driver-2', status: 'pending' },
          { id: 'protest-2', protestingDriverId: 'driver-3', accusedDriverId: 'driver-4', status: 'resolved' },
        ],
        driverMap: {},
      };

      mockApiClient.getRaceProtests.mockResolvedValue(mockDto as any);

      const result = await service.findByRaceId(raceId);

      expect(mockApiClient.getRaceProtests).toHaveBeenCalledWith(raceId);
      expect(result).toEqual(mockDto.protests);
      expect(result).toHaveLength(2);
    });

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

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