import { describe, it, expect, vi, beforeEach } from 'vitest';
import { TeamService } from './TeamService';
import type { TeamsApiClient } from '../../api/teams/TeamsApiClient';
import type { TeamDetailsPresenter } from '../../presenters/TeamDetailsPresenter';
import type { TeamListPresenter } from '../../presenters/TeamListPresenter';
import type { TeamMembersPresenter } from '../../presenters/TeamMembersPresenter';
import type {
  AllTeamsDto,
  TeamDetailsDto,
  TeamMembersDto,
  CreateTeamInputDto,
  CreateTeamOutputDto,
  UpdateTeamInputDto,
  UpdateTeamOutputDto,
  DriverTeamDto,
} from '../../dtos';
import type { TeamSummaryViewModel, TeamDetailsViewModel, TeamMemberViewModel } from '../../view-models';

describe('TeamService', () => {
  let service: TeamService;
  let mockApiClient: TeamsApiClient;
  let mockTeamListPresenter: TeamListPresenter;
  let mockTeamDetailsPresenter: TeamDetailsPresenter;
  let mockTeamMembersPresenter: TeamMembersPresenter;

  beforeEach(() => {
    mockApiClient = {
      getAll: vi.fn(),
      getDetails: vi.fn(),
      getMembers: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      getDriverTeam: vi.fn(),
    } as unknown as TeamsApiClient;

    mockTeamListPresenter = {
      present: vi.fn(),
    } as unknown as TeamListPresenter;

    mockTeamDetailsPresenter = {
      present: vi.fn(),
    } as unknown as TeamDetailsPresenter;

    mockTeamMembersPresenter = {
      present: vi.fn(),
    } as unknown as TeamMembersPresenter;

    service = new TeamService(
      mockApiClient,
      mockTeamListPresenter,
      mockTeamDetailsPresenter,
      mockTeamMembersPresenter
    );
  });

  describe('constructor', () => {
    it('should create instance with injected dependencies', () => {
      expect(service).toBeInstanceOf(TeamService);
    });
  });

  describe('getAllTeams', () => {
    it('should fetch all teams from API and transform via presenter', async () => {
      // Arrange
      const mockDto: AllTeamsDto = {
        teams: [
          {
            id: 'team-1',
            name: 'Team Alpha',
            logoUrl: 'https://example.com/logo1.png',
            memberCount: 5,
            rating: 2500,
          },
          {
            id: 'team-2',
            name: 'Team Beta',
            logoUrl: 'https://example.com/logo2.png',
            memberCount: 3,
            rating: 2300,
          },
        ],
      };

      const mockViewModels: TeamSummaryViewModel[] = [
        {
          id: 'team-1',
          name: 'Team Alpha',
          logoUrl: 'https://example.com/logo1.png',
          memberCount: 5,
          rating: 2500,
        } as TeamSummaryViewModel,
        {
          id: 'team-2',
          name: 'Team Beta',
          logoUrl: 'https://example.com/logo2.png',
          memberCount: 3,
          rating: 2300,
        } as TeamSummaryViewModel,
      ];

      vi.mocked(mockApiClient.getAll).mockResolvedValue(mockDto);
      vi.mocked(mockTeamListPresenter.present).mockReturnValue(mockViewModels);

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

      // Assert
      expect(mockApiClient.getAll).toHaveBeenCalled();
      expect(mockTeamListPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toEqual(mockViewModels);
    });

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

      const mockViewModels: TeamSummaryViewModel[] = [];

      vi.mocked(mockApiClient.getAll).mockResolvedValue(mockDto);
      vi.mocked(mockTeamListPresenter.present).mockReturnValue(mockViewModels);

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

      // Assert
      expect(mockApiClient.getAll).toHaveBeenCalled();
      expect(mockTeamListPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toEqual([]);
    });

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

      // Act & Assert
      await expect(service.getAllTeams()).rejects.toThrow('Failed to fetch teams');
      expect(mockApiClient.getAll).toHaveBeenCalled();
      expect(mockTeamListPresenter.present).not.toHaveBeenCalled();
    });
  });

  describe('getTeamDetails', () => {
    it('should fetch team details and transform via presenter', async () => {
      // Arrange
      const teamId = 'team-123';
      const currentUserId = 'user-456';

      const mockDto: TeamDetailsDto = {
        id: teamId,
        name: 'Team Alpha',
        description: 'A competitive racing team',
        logoUrl: 'https://example.com/logo.png',
        memberCount: 5,
        ownerId: 'user-789',
      };

      const mockViewModel: TeamDetailsViewModel = {
        id: teamId,
        name: 'Team Alpha',
        description: 'A competitive racing team',
        logoUrl: 'https://example.com/logo.png',
        memberCount: 5,
        ownerId: 'user-789',
        members: [],
      } as TeamDetailsViewModel;

      vi.mocked(mockApiClient.getDetails).mockResolvedValue(mockDto);
      vi.mocked(mockTeamDetailsPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getTeamDetails(teamId, currentUserId);

      // Assert
      expect(mockApiClient.getDetails).toHaveBeenCalledWith(teamId);
      expect(mockTeamDetailsPresenter.present).toHaveBeenCalledWith(mockDto, currentUserId);
      expect(result).toEqual(mockViewModel);
    });

    it('should return null when team is not found', async () => {
      // Arrange
      const teamId = 'non-existent';
      const currentUserId = 'user-456';

      vi.mocked(mockApiClient.getDetails).mockResolvedValue(null);

      // Act
      const result = await service.getTeamDetails(teamId, currentUserId);

      // Assert
      expect(mockApiClient.getDetails).toHaveBeenCalledWith(teamId);
      expect(mockTeamDetailsPresenter.present).not.toHaveBeenCalled();
      expect(result).toBeNull();
    });

    it('should propagate errors from API client', async () => {
      // Arrange
      const teamId = 'team-123';
      const currentUserId = 'user-456';
      const error = new Error('Failed to fetch team details');
      vi.mocked(mockApiClient.getDetails).mockRejectedValue(error);

      // Act & Assert
      await expect(service.getTeamDetails(teamId, currentUserId)).rejects.toThrow('Failed to fetch team details');
      expect(mockApiClient.getDetails).toHaveBeenCalledWith(teamId);
      expect(mockTeamDetailsPresenter.present).not.toHaveBeenCalled();
    });
  });

  describe('getTeamMembers', () => {
    it('should fetch team members and transform via presenter', async () => {
      // Arrange
      const teamId = 'team-123';
      const currentUserId = 'user-456';
      const teamOwnerId = 'user-789';

      const mockDto: TeamMembersDto = {
        members: [
          {
            driverId: 'driver-1',
            role: 'owner',
            joinedAt: '2024-01-01',
          },
          {
            driverId: 'driver-2',
            role: 'member',
            joinedAt: '2024-01-02',
          },
        ],
      };

      const mockViewModels: TeamMemberViewModel[] = [
        {
          driverId: 'driver-1',
          role: 'owner',
          joinedAt: '2024-01-01',
        } as TeamMemberViewModel,
        {
          driverId: 'driver-2',
          role: 'member',
          joinedAt: '2024-01-02',
        } as TeamMemberViewModel,
      ];

      vi.mocked(mockApiClient.getMembers).mockResolvedValue(mockDto);
      vi.mocked(mockTeamMembersPresenter.present).mockReturnValue(mockViewModels);

      // Act
      const result = await service.getTeamMembers(teamId, currentUserId, teamOwnerId);

      // Assert
      expect(mockApiClient.getMembers).toHaveBeenCalledWith(teamId);
      expect(mockTeamMembersPresenter.present).toHaveBeenCalledWith(mockDto, currentUserId, teamOwnerId);
      expect(result).toEqual(mockViewModels);
    });

    it('should handle empty members list', async () => {
      // Arrange
      const teamId = 'team-123';
      const currentUserId = 'user-456';
      const teamOwnerId = 'user-789';

      const mockDto: TeamMembersDto = {
        members: [],
      };

      const mockViewModels: TeamMemberViewModel[] = [];

      vi.mocked(mockApiClient.getMembers).mockResolvedValue(mockDto);
      vi.mocked(mockTeamMembersPresenter.present).mockReturnValue(mockViewModels);

      // Act
      const result = await service.getTeamMembers(teamId, currentUserId, teamOwnerId);

      // Assert
      expect(mockApiClient.getMembers).toHaveBeenCalledWith(teamId);
      expect(mockTeamMembersPresenter.present).toHaveBeenCalledWith(mockDto, currentUserId, teamOwnerId);
      expect(result).toEqual([]);
    });

    it('should propagate errors from API client', async () => {
      // Arrange
      const teamId = 'team-123';
      const currentUserId = 'user-456';
      const teamOwnerId = 'user-789';
      const error = new Error('Failed to fetch team members');
      vi.mocked(mockApiClient.getMembers).mockRejectedValue(error);

      // Act & Assert
      await expect(service.getTeamMembers(teamId, currentUserId, teamOwnerId)).rejects.toThrow('Failed to fetch team members');
      expect(mockApiClient.getMembers).toHaveBeenCalledWith(teamId);
      expect(mockTeamMembersPresenter.present).not.toHaveBeenCalled();
    });
  });

  describe('createTeam', () => {
    it('should create a new team', async () => {
      // Arrange
      const input: CreateTeamInputDto = {
        name: 'New Team',
        description: 'A new racing team',
      };

      const mockOutput: CreateTeamOutputDto = {
        id: 'team-new',
        name: 'New Team',
        success: true,
      };

      vi.mocked(mockApiClient.create).mockResolvedValue(mockOutput);

      // Act
      const result = await service.createTeam(input);

      // Assert
      expect(mockApiClient.create).toHaveBeenCalledWith(input);
      expect(result).toEqual(mockOutput);
    });

    it('should propagate errors from API client', async () => {
      // Arrange
      const input: CreateTeamInputDto = {
        name: 'New Team',
        description: 'A new racing team',
      };
      const error = new Error('Failed to create team');
      vi.mocked(mockApiClient.create).mockRejectedValue(error);

      // Act & Assert
      await expect(service.createTeam(input)).rejects.toThrow('Failed to create team');
      expect(mockApiClient.create).toHaveBeenCalledWith(input);
    });
  });

  describe('updateTeam', () => {
    it('should update team details', async () => {
      // Arrange
      const teamId = 'team-123';
      const input: UpdateTeamInputDto = {
        name: 'Updated Team Name',
        description: 'Updated description',
      };

      const mockOutput: UpdateTeamOutputDto = {
        id: teamId,
        success: true,
      };

      vi.mocked(mockApiClient.update).mockResolvedValue(mockOutput);

      // Act
      const result = await service.updateTeam(teamId, input);

      // Assert
      expect(mockApiClient.update).toHaveBeenCalledWith(teamId, input);
      expect(result).toEqual(mockOutput);
    });

    it('should propagate errors from API client', async () => {
      // Arrange
      const teamId = 'team-123';
      const input: UpdateTeamInputDto = {
        name: 'Updated Team Name',
      };
      const error = new Error('Failed to update team');
      vi.mocked(mockApiClient.update).mockRejectedValue(error);

      // Act & Assert
      await expect(service.updateTeam(teamId, input)).rejects.toThrow('Failed to update team');
      expect(mockApiClient.update).toHaveBeenCalledWith(teamId, input);
    });
  });

  describe('getDriverTeam', () => {
    it('should fetch driver team', async () => {
      // Arrange
      const driverId = 'driver-123';

      const mockDto: DriverTeamDto = {
        teamId: 'team-456',
        teamName: 'Team Alpha',
        role: 'member',
      };

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

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

      // Assert
      expect(mockApiClient.getDriverTeam).toHaveBeenCalledWith(driverId);
      expect(result).toEqual(mockDto);
    });

    it('should return null when driver has no team', async () => {
      // Arrange
      const driverId = 'driver-123';

      vi.mocked(mockApiClient.getDriverTeam).mockResolvedValue(null);

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

      // Assert
      expect(mockApiClient.getDriverTeam).toHaveBeenCalledWith(driverId);
      expect(result).toBeNull();
    });

    it('should propagate errors from API client', async () => {
      // Arrange
      const driverId = 'driver-123';
      const error = new Error('Failed to fetch driver team');
      vi.mocked(mockApiClient.getDriverTeam).mockRejectedValue(error);

      // Act & Assert
      await expect(service.getDriverTeam(driverId)).rejects.toThrow('Failed to fetch driver team');
      expect(mockApiClient.getDriverTeam).toHaveBeenCalledWith(driverId);
    });
  });
});