import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AuthService } from './AuthService';
import { AuthApiClient } from '../../api/auth/AuthApiClient';
import type { LoginParamsDto, SignupParamsDto, SessionDataDto } from '../../dtos';

describe('AuthService', () => {
  let mockApiClient: AuthApiClient;
  let service: AuthService;

  beforeEach(() => {
    mockApiClient = {
      signup: vi.fn(),
      login: vi.fn(),
      logout: vi.fn(),
      getSession: vi.fn(),
      getIracingAuthUrl: vi.fn(),
    } as unknown as AuthApiClient;

    service = new AuthService(mockApiClient);
  });

  describe('signup', () => {
    it('should sign up user via API client', async () => {
      // Arrange
      const params: SignupParamsDto = {
        email: 'test@example.com',
        password: 'password123',
        displayName: 'Test User',
      };

      const expectedSession: SessionDataDto = {
        userId: 'user-1',
        email: 'test@example.com',
        displayName: 'Test User',
        isAuthenticated: true,
      };

      vi.mocked(mockApiClient.signup).mockResolvedValue(expectedSession);

      // Act
      const result = await service.signup(params);

      // Assert
      expect(mockApiClient.signup).toHaveBeenCalledWith(params);
      expect(mockApiClient.signup).toHaveBeenCalledTimes(1);
      expect(result).toBe(expectedSession);
    });

    it('should propagate API client errors', async () => {
      // Arrange
      const params: SignupParamsDto = {
        email: 'test@example.com',
        password: 'password123',
      };

      const error = new Error('API Error: Failed to sign up');
      vi.mocked(mockApiClient.signup).mockRejectedValue(error);

      // Act & Assert
      await expect(service.signup(params)).rejects.toThrow(
        'API Error: Failed to sign up'
      );

      expect(mockApiClient.signup).toHaveBeenCalledWith(params);
      expect(mockApiClient.signup).toHaveBeenCalledTimes(1);
    });
  });

  describe('login', () => {
    it('should log in user via API client', async () => {
      // Arrange
      const params: LoginParamsDto = {
        email: 'test@example.com',
        password: 'password123',
      };

      const expectedSession: SessionDataDto = {
        userId: 'user-1',
        email: 'test@example.com',
        isAuthenticated: true,
      };

      vi.mocked(mockApiClient.login).mockResolvedValue(expectedSession);

      // Act
      const result = await service.login(params);

      // Assert
      expect(mockApiClient.login).toHaveBeenCalledWith(params);
      expect(mockApiClient.login).toHaveBeenCalledTimes(1);
      expect(result).toBe(expectedSession);
    });

    it('should propagate API client errors', async () => {
      // Arrange
      const params: LoginParamsDto = {
        email: 'test@example.com',
        password: 'password123',
      };

      const error = new Error('API Error: Invalid credentials');
      vi.mocked(mockApiClient.login).mockRejectedValue(error);

      // Act & Assert
      await expect(service.login(params)).rejects.toThrow(
        'API Error: Invalid credentials'
      );

      expect(mockApiClient.login).toHaveBeenCalledWith(params);
      expect(mockApiClient.login).toHaveBeenCalledTimes(1);
    });
  });

  describe('logout', () => {
    it('should log out user via API client', async () => {
      // Arrange
      vi.mocked(mockApiClient.logout).mockResolvedValue(undefined);

      // Act
      await service.logout();

      // Assert
      expect(mockApiClient.logout).toHaveBeenCalledTimes(1);
    });

    it('should propagate API client errors', async () => {
      // Arrange
      const error = new Error('API Error: Failed to logout');
      vi.mocked(mockApiClient.logout).mockRejectedValue(error);

      // Act & Assert
      await expect(service.logout()).rejects.toThrow(
        'API Error: Failed to logout'
      );

      expect(mockApiClient.logout).toHaveBeenCalledTimes(1);
    });
  });

  describe('getIracingAuthUrl', () => {
    it('should get iRacing auth URL via API client', () => {
      // Arrange
      const returnTo = '/dashboard';
      const expectedUrl = 'http://localhost:3001/auth/iracing/start?returnTo=%2Fdashboard';

      vi.mocked(mockApiClient.getIracingAuthUrl).mockReturnValue(expectedUrl);

      // Act
      const result = service.getIracingAuthUrl(returnTo);

      // Assert
      expect(mockApiClient.getIracingAuthUrl).toHaveBeenCalledWith(returnTo);
      expect(mockApiClient.getIracingAuthUrl).toHaveBeenCalledTimes(1);
      expect(result).toBe(expectedUrl);
    });

    it('should handle undefined returnTo', () => {
      // Arrange
      const expectedUrl = 'http://localhost:3001/auth/iracing/start';

      vi.mocked(mockApiClient.getIracingAuthUrl).mockReturnValue(expectedUrl);

      // Act
      const result = service.getIracingAuthUrl();

      // Assert
      expect(mockApiClient.getIracingAuthUrl).toHaveBeenCalledWith(undefined);
      expect(result).toBe(expectedUrl);
    });
  });

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

    it('should use injected apiClient', async () => {
      // Arrange
      const customApiClient = {
        signup: vi.fn().mockResolvedValue({ userId: 'user-1', email: 'test@example.com', isAuthenticated: true }),
        login: vi.fn(),
        logout: vi.fn(),
        getSession: vi.fn(),
        getIracingAuthUrl: vi.fn(),
      } as unknown as AuthApiClient;

      const customService = new AuthService(customApiClient);

      const params: SignupParamsDto = {
        email: 'test@example.com',
        password: 'password123',
      };

      // Act
      await customService.signup(params);

      // Assert
      expect(customApiClient.signup).toHaveBeenCalledWith(params);
    });
  });
});