import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SessionService } from './SessionService';
import { AuthApiClient } from '../../api/auth/AuthApiClient';
import { SessionPresenter } from '../../presenters/SessionPresenter';
import { SessionViewModel } from '../../view-models';
import type { SessionDataDto } from '../../dtos';

describe('SessionService', () => {
  let mockApiClient: AuthApiClient;
  let mockPresenter: SessionPresenter;
  let service: SessionService;

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

    mockPresenter = {
      presentSession: vi.fn(),
    } as unknown as SessionPresenter;

    service = new SessionService(mockApiClient, mockPresenter);
  });

  describe('getSession', () => {
    it('should get session via API client and present it', async () => {
      // Arrange
      const dto: SessionDataDto = {
        userId: 'user-1',
        email: 'test@example.com',
        displayName: 'Test User',
        driverId: 'driver-1',
        isAuthenticated: true,
      };

      const expectedViewModel = new SessionViewModel(dto);

      vi.mocked(mockApiClient.getSession).mockResolvedValue(dto);
      vi.mocked(mockPresenter.presentSession).mockReturnValue(expectedViewModel);

      // Act
      const result = await service.getSession();

      // Assert
      expect(mockApiClient.getSession).toHaveBeenCalledTimes(1);
      expect(mockPresenter.presentSession).toHaveBeenCalledWith(dto);
      expect(mockPresenter.presentSession).toHaveBeenCalledTimes(1);
      expect(result).toBe(expectedViewModel);
    });

    it('should return null when session is null', async () => {
      // Arrange
      vi.mocked(mockApiClient.getSession).mockResolvedValue(null);
      vi.mocked(mockPresenter.presentSession).mockReturnValue(null);

      // Act
      const result = await service.getSession();

      // Assert
      expect(mockApiClient.getSession).toHaveBeenCalledTimes(1);
      expect(mockPresenter.presentSession).toHaveBeenCalledWith(null);
      expect(result).toBeNull();
    });

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

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

      expect(mockApiClient.getSession).toHaveBeenCalledTimes(1);
      expect(mockPresenter.presentSession).not.toHaveBeenCalled();
    });

    it('should handle different session data', async () => {
      // Arrange
      const dto: SessionDataDto = {
        userId: 'user-2',
        email: 'another@example.com',
        isAuthenticated: false,
      };

      const expectedViewModel = new SessionViewModel(dto);

      vi.mocked(mockApiClient.getSession).mockResolvedValue(dto);
      vi.mocked(mockPresenter.presentSession).mockReturnValue(expectedViewModel);

      // Act
      const result = await service.getSession();

      // Assert
      expect(mockApiClient.getSession).toHaveBeenCalledTimes(1);
      expect(mockPresenter.presentSession).toHaveBeenCalledWith(dto);
      expect(result).toBe(expectedViewModel);
    });
  });

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

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

      const customPresenter = {
        presentSession: vi.fn().mockReturnValue(new SessionViewModel({ userId: 'user-1', email: 'test@example.com', isAuthenticated: true })),
      } as unknown as SessionPresenter;

      const customService = new SessionService(customApiClient, customPresenter);

      // Act
      await customService.getSession();

      // Assert
      expect(customApiClient.getSession).toHaveBeenCalledTimes(1);
      expect(customPresenter.presentSession).toHaveBeenCalledTimes(1);
    });
  });
});