import { describe, it, expect, vi, beforeEach } from 'vitest';
import { WalletService } from './WalletService';
import { PaymentsApiClient } from '../../api/payments/PaymentsApiClient';
import type { GetWalletOutputDto } from '../../dtos';

import { presentWallet } from '../../presenters/WalletPresenter';

// Mock the presenter
vi.mock('../../presenters/WalletPresenter', () => ({
  presentWallet: vi.fn(),
}));

describe('WalletService', () => {
  let mockApiClient: PaymentsApiClient;
  let service: WalletService;

  beforeEach(() => {
    mockApiClient = {
      getWallet: vi.fn(),
      getMembershipFees: vi.fn(),
      getPayments: vi.fn(),
      createPayment: vi.fn(),
      getPrizes: vi.fn(),
    } as unknown as PaymentsApiClient;

    service = new WalletService(mockApiClient);
  });

  describe('getWallet', () => {
    it('should get wallet via API client and present it', async () => {
      // Arrange
      const driverId = 'driver-1';
      const dto: GetWalletOutputDto = {
        balance: 1000,
        currency: 'USD',
        transactions: [],
      };

      const expectedViewModel = {
        balance: 1000,
        currency: 'USD',
        transactions: [],
      };

      vi.mocked(mockApiClient.getWallet).mockResolvedValue(dto);

      vi.mocked(presentWallet).mockReturnValue(expectedViewModel);

      // Act
      const result = await service.getWallet(driverId);

      // Assert
      expect(mockApiClient.getWallet).toHaveBeenCalledWith(driverId);
      expect(mockApiClient.getWallet).toHaveBeenCalledTimes(1);
      expect(presentWallet).toHaveBeenCalledWith(dto);
      expect(result).toBe(expectedViewModel);
    });

    it('should propagate API client errors', async () => {
      // Arrange
      const driverId = 'driver-1';
      const error = new Error('API Error: Failed to get wallet');
      vi.mocked(mockApiClient.getWallet).mockRejectedValue(error);

      // Act & Assert
      await expect(service.getWallet(driverId)).rejects.toThrow(
        'API Error: Failed to get wallet'
      );

      expect(mockApiClient.getWallet).toHaveBeenCalledWith(driverId);
      expect(mockApiClient.getWallet).toHaveBeenCalledTimes(1);
    });

    it('should handle different driver IDs', async () => {
      // Arrange
      const driverId = 'driver-2';
      const dto: GetWalletOutputDto = {
        balance: 500,
        currency: 'EUR',
        transactions: [],
      };

      const expectedViewModel = {
        balance: 500,
        currency: 'EUR',
        transactions: [],
      };

      vi.mocked(mockApiClient.getWallet).mockResolvedValue(dto);

      vi.mocked(presentWallet).mockReturnValue(expectedViewModel);

      // Act
      const result = await service.getWallet(driverId);

      // Assert
      expect(mockApiClient.getWallet).toHaveBeenCalledWith(driverId);
      expect(result).toBe(expectedViewModel);
    });
  });

  describe('Constructor Dependency Injection', () => {
    it('should require apiClient', () => {
      // This test verifies the constructor signature
      expect(() => {
        new WalletService(mockApiClient);
      }).not.toThrow();
    });

    it('should use injected apiClient', async () => {
      // Arrange
      const customApiClient = {
        getWallet: vi.fn().mockResolvedValue({ balance: 200, currency: 'USD', transactions: [] }),
        getMembershipFees: vi.fn(),
        getPayments: vi.fn(),
        createPayment: vi.fn(),
        getPrizes: vi.fn(),
      } as unknown as PaymentsApiClient;

      const customService = new WalletService(customApiClient);

      vi.mocked(presentWallet).mockReturnValue({ balance: 200, currency: 'USD', transactions: [] });

      // Act
      await customService.getWallet('driver-1');

      // Assert
      expect(customApiClient.getWallet).toHaveBeenCalledWith('driver-1');
    });
  });
});