import { describe, it, expect, vi, beforeEach } from 'vitest';
import { TeamJoinService } from './TeamJoinService';
import { TeamsApiClient } from '../../api/teams/TeamsApiClient';
import { TeamJoinRequestPresenter } from '../../presenters/TeamJoinRequestPresenter';
import type { TeamJoinRequestsDto, TeamJoinRequestItemDto } from '../../dtos';
import type { TeamJoinRequestViewModel } from '../../view-models';

describe('TeamJoinService', () => {
  let mockApiClient: TeamsApiClient;
  let mockPresenter: TeamJoinRequestPresenter;
  let service: TeamJoinService;

  beforeEach(() => {
    mockApiClient = {
      getJoinRequests: vi.fn(),
    } as unknown as TeamsApiClient;

    mockPresenter = {
      present: vi.fn(),
    } as unknown as TeamJoinRequestPresenter;

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

  describe('getJoinRequests', () => {
    it('should fetch join requests from API and transform via presenter', async () => {
      // Arrange
      const mockRequestDto: TeamJoinRequestItemDto = {
        id: 'request-1',
        teamId: 'team-1',
        driverId: 'driver-1',
        requestedAt: '2025-12-17T20:00:00Z',
        message: 'Please let me join',
      };

      const mockDto: TeamJoinRequestsDto = {
        requests: [mockRequestDto],
      };

      const mockViewModel: TeamJoinRequestViewModel = {
        id: 'request-1',
        teamId: 'team-1',
        driverId: 'driver-1',
        requestedAt: '2025-12-17T20:00:00Z',
        message: 'Please let me join',
        canApprove: true,
        formattedRequestedAt: '12/17/2025, 9:00:00 PM',
        status: 'Pending',
        statusColor: 'yellow',
        approveButtonText: 'Approve',
        rejectButtonText: 'Reject',
      } as unknown as TeamJoinRequestViewModel;

      vi.mocked(mockApiClient.getJoinRequests).mockResolvedValue(mockDto);
      vi.mocked(mockPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getJoinRequests('team-1', 'driver-owner', true);

      // Assert
      expect(mockApiClient.getJoinRequests).toHaveBeenCalledWith('team-1');
      expect(mockApiClient.getJoinRequests).toHaveBeenCalledTimes(1);
      expect(mockPresenter.present).toHaveBeenCalledWith(mockRequestDto, 'driver-owner', true);
      expect(mockPresenter.present).toHaveBeenCalledTimes(1);
      expect(result).toEqual([mockViewModel]);
    });

    it('should handle multiple join requests', async () => {
      // Arrange
      const mockRequestDto1: TeamJoinRequestItemDto = {
        id: 'request-1',
        teamId: 'team-1',
        driverId: 'driver-1',
        requestedAt: '2025-12-17T20:00:00Z',
      };

      const mockRequestDto2: TeamJoinRequestItemDto = {
        id: 'request-2',
        teamId: 'team-1',
        driverId: 'driver-2',
        requestedAt: '2025-12-17T21:00:00Z',
        message: 'I want to join',
      };

      const mockDto: TeamJoinRequestsDto = {
        requests: [mockRequestDto1, mockRequestDto2],
      };

      const mockViewModel1 = { id: 'request-1' } as TeamJoinRequestViewModel;
      const mockViewModel2 = { id: 'request-2' } as TeamJoinRequestViewModel;

      vi.mocked(mockApiClient.getJoinRequests).mockResolvedValue(mockDto);
      vi.mocked(mockPresenter.present)
        .mockReturnValueOnce(mockViewModel1)
        .mockReturnValueOnce(mockViewModel2);

      // Act
      const result = await service.getJoinRequests('team-1', 'driver-owner', true);

      // Assert
      expect(mockApiClient.getJoinRequests).toHaveBeenCalledWith('team-1');
      expect(mockPresenter.present).toHaveBeenCalledTimes(2);
      expect(mockPresenter.present).toHaveBeenNthCalledWith(1, mockRequestDto1, 'driver-owner', true);
      expect(mockPresenter.present).toHaveBeenNthCalledWith(2, mockRequestDto2, 'driver-owner', true);
      expect(result).toEqual([mockViewModel1, mockViewModel2]);
    });

    it('should handle empty join requests list', async () => {
      // Arrange
      const mockDto: TeamJoinRequestsDto = {
        requests: [],
      };

      vi.mocked(mockApiClient.getJoinRequests).mockResolvedValue(mockDto);

      // Act
      const result = await service.getJoinRequests('team-1', 'driver-owner', true);

      // Assert
      expect(mockApiClient.getJoinRequests).toHaveBeenCalledWith('team-1');
      expect(mockPresenter.present).not.toHaveBeenCalled();
      expect(result).toEqual([]);
    });

    it('should propagate API client errors', async () => {
      // Arrange
      const error = new Error('API Error: Team not found');
      vi.mocked(mockApiClient.getJoinRequests).mockRejectedValue(error);

      // Act & Assert
      await expect(
        service.getJoinRequests('invalid-team', 'driver-1', false)
      ).rejects.toThrow('API Error: Team not found');

      expect(mockApiClient.getJoinRequests).toHaveBeenCalledWith('invalid-team');
      expect(mockPresenter.present).not.toHaveBeenCalled();
    });

    it('should propagate presenter errors', async () => {
      // Arrange
      const mockRequestDto: TeamJoinRequestItemDto = {
        id: 'request-1',
        teamId: 'team-1',
        driverId: 'driver-1',
        requestedAt: '2025-12-17T20:00:00Z',
      };

      const mockDto: TeamJoinRequestsDto = {
        requests: [mockRequestDto],
      };

      const error = new Error('Presenter Error: Invalid DTO structure');
      vi.mocked(mockApiClient.getJoinRequests).mockResolvedValue(mockDto);
      vi.mocked(mockPresenter.present).mockImplementation(() => {
        throw error;
      });

      // Act & Assert
      await expect(
        service.getJoinRequests('team-1', 'driver-1', false)
      ).rejects.toThrow('Presenter Error: Invalid DTO structure');

      expect(mockApiClient.getJoinRequests).toHaveBeenCalledWith('team-1');
      expect(mockPresenter.present).toHaveBeenCalledWith(mockRequestDto, 'driver-1', false);
    });

    it('should pass correct isOwner flag to presenter', async () => {
      // Arrange
      const mockRequestDto: TeamJoinRequestItemDto = {
        id: 'request-1',
        teamId: 'team-1',
        driverId: 'driver-1',
        requestedAt: '2025-12-17T20:00:00Z',
      };

      const mockDto: TeamJoinRequestsDto = {
        requests: [mockRequestDto],
      };

      const mockViewModel = { id: 'request-1' } as TeamJoinRequestViewModel;

      vi.mocked(mockApiClient.getJoinRequests).mockResolvedValue(mockDto);
      vi.mocked(mockPresenter.present).mockReturnValue(mockViewModel);

      // Act - non-owner
      await service.getJoinRequests('team-1', 'driver-member', false);

      // Assert
      expect(mockPresenter.present).toHaveBeenCalledWith(mockRequestDto, 'driver-member', false);
    });
  });

  describe('approveJoinRequest', () => {
    it('should throw not implemented error', async () => {
      // Act & Assert
      await expect(
        service.approveJoinRequest('team-1', 'request-1')
      ).rejects.toThrow('Not implemented: API endpoint for approving join requests');
    });

    it('should propagate errors when API is implemented', async () => {
      // This test ensures error handling is in place for future implementation
      // Act & Assert
      await expect(
        service.approveJoinRequest('team-1', 'request-1')
      ).rejects.toThrow();
    });
  });

  describe('rejectJoinRequest', () => {
    it('should throw not implemented error', async () => {
      // Act & Assert
      await expect(
        service.rejectJoinRequest('team-1', 'request-1')
      ).rejects.toThrow('Not implemented: API endpoint for rejecting join requests');
    });

    it('should propagate errors when API is implemented', async () => {
      // This test ensures error handling is in place for future implementation
      // Act & Assert
      await expect(
        service.rejectJoinRequest('team-1', 'request-1')
      ).rejects.toThrow();
    });
  });

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

    it('should use injected dependencies', async () => {
      // Arrange
      const customApiClient = {
        getJoinRequests: vi.fn().mockResolvedValue({ requests: [] }),
      } as unknown as TeamsApiClient;

      const customPresenter = {
        present: vi.fn().mockReturnValue({} as TeamJoinRequestViewModel),
      } as unknown as TeamJoinRequestPresenter;

      const customService = new TeamJoinService(customApiClient, customPresenter);

      // Act
      await customService.getJoinRequests('team-1', 'driver-1', true);

      // Assert
      expect(customApiClient.getJoinRequests).toHaveBeenCalledWith('team-1');
    });
  });
});