import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { GetAllTeamsUseCase, type GetAllTeamsInput, type GetAllTeamsResult } from './GetAllTeamsUseCase';
import type { ITeamRepository } from '../../domain/repositories/ITeamRepository';
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
import type { ITeamStatsRepository } from '../../domain/repositories/ITeamStatsRepository';
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
import type { Logger } from '@core/shared/application';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';

describe('GetAllTeamsUseCase', () => {
  const mockTeamFindAll = vi.fn();
  const mockTeamRepo: ITeamRepository = {
    findById: vi.fn(),
    findAll: mockTeamFindAll,
    findByLeagueId: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
    exists: vi.fn(),
  };

  const mockTeamMembershipCountByTeamId = vi.fn();
  const mockTeamMembershipRepo: ITeamMembershipRepository = {
    getMembership: vi.fn(),
    getActiveMembershipForDriver: vi.fn(),
    getTeamMembers: vi.fn(),
    saveMembership: vi.fn(),
    removeMembership: vi.fn(),
    countByTeamId: mockTeamMembershipCountByTeamId,
    getJoinRequests: vi.fn(),
    saveJoinRequest: vi.fn(),
    removeJoinRequest: vi.fn(),
  };

  const mockTeamStatsRepo: ITeamStatsRepository = {
    getTeamStats: vi.fn(),
    saveTeamStats: vi.fn(),
    getAllStats: vi.fn(),
    clear: vi.fn(),
  };

  const mockResultRepo: IResultRepository = {
    findAll: vi.fn(),
    findById: vi.fn(),
    findByRaceId: vi.fn(),
    findByDriverId: vi.fn(),
    findByDriverIdAndLeagueId: vi.fn(),
    create: vi.fn(),
    createMany: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
    deleteByRaceId: vi.fn(),
    exists: vi.fn(),
    existsByRaceId: vi.fn(),
  };

  const mockLogger: Logger = {
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
  };

  let output: UseCaseOutputPort<GetAllTeamsResult> & { present: Mock };

  beforeEach(() => {
    vi.clearAllMocks();
    output = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<GetAllTeamsResult> & { present: Mock };
  });

  it('should return teams data', async () => {
    const useCase = new GetAllTeamsUseCase(
      mockTeamRepo,
      mockTeamMembershipRepo,
      mockTeamStatsRepo,
      mockResultRepo,
      mockLogger,
      output,
    );

    const team1 = {
      id: 'team1',
      name: { props: 'Team One' },
      tag: { props: 'TO' },
      description: { props: 'Description One' },
      ownerId: { toString: () => 'owner1' },
      leagues: [{ toString: () => 'league1' }],
      createdAt: { toDate: () => new Date('2023-01-01T00:00:00Z') },
      logoRef: { toJSON: () => ({ type: 'generated', generationRequestId: 'team-team1' }) },
      category: undefined,
      isRecruiting: false,
    };
    const team2 = {
      id: 'team2',
      name: { props: 'Team Two' },
      tag: { props: 'TT' },
      description: { props: 'Description Two' },
      ownerId: { toString: () => 'owner2' },
      leagues: [{ toString: () => 'league2' }],
      createdAt: { toDate: () => new Date('2023-01-02T00:00:00Z') },
      logoRef: { toJSON: () => ({ type: 'generated', generationRequestId: 'team-team2' }) },
      category: undefined,
      isRecruiting: true,
    };

    mockTeamFindAll.mockResolvedValue([team1, team2]);
    mockTeamMembershipCountByTeamId.mockImplementation((id: string) => Promise.resolve(id === 'team1' ? 5 : 3));

    // Provide precomputed stats so the use case doesn't compute from results.
    (mockTeamStatsRepo.getTeamStats as unknown as Mock).mockImplementation((teamId: string) =>
      Promise.resolve(
        teamId === 'team1'
          ? {
              performanceLevel: 'intermediate',
              specialization: 'mixed',
              region: 'EU',
              languages: ['en'],
              totalWins: 2,
              totalRaces: 10,
              rating: 1200,
            }
          : {
              performanceLevel: 'advanced',
              specialization: 'mixed',
              region: 'US',
              languages: ['en', 'de'],
              totalWins: 5,
              totalRaces: 20,
              rating: 1400,
            },
      ),
    );

    const result = await useCase.execute({} as GetAllTeamsInput);

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();

    expect(output.present).toHaveBeenCalledTimes(1);
    const presented = output.present.mock.calls[0]?.[0] as GetAllTeamsResult;

    expect(presented).toEqual({
      teams: [
        {
          id: 'team1',
          name: 'Team One',
          tag: 'TO',
          description: 'Description One',
          ownerId: 'owner1',
          leagues: ['league1'],
          createdAt: new Date('2023-01-01T00:00:00Z'),
          memberCount: 5,
          totalWins: 2,
          totalRaces: 10,
          performanceLevel: 'intermediate',
          specialization: 'mixed',
          region: 'EU',
          languages: ['en'],
          logoRef: team1.logoRef,
          logoUrl: null,
          rating: 1200,
          category: undefined,
          isRecruiting: false,
        },
        {
          id: 'team2',
          name: 'Team Two',
          tag: 'TT',
          description: 'Description Two',
          ownerId: 'owner2',
          leagues: ['league2'],
          createdAt: new Date('2023-01-02T00:00:00Z'),
          memberCount: 3,
          totalWins: 5,
          totalRaces: 20,
          performanceLevel: 'advanced',
          specialization: 'mixed',
          region: 'US',
          languages: ['en', 'de'],
          logoRef: team2.logoRef,
          logoUrl: null,
          rating: 1400,
          category: undefined,
          isRecruiting: true,
        },
      ],
      totalCount: 2,
    });
  });

  it('should return empty result when no teams', async () => {
    const useCase = new GetAllTeamsUseCase(
      mockTeamRepo,
      mockTeamMembershipRepo,
      mockTeamStatsRepo,
      mockResultRepo,
      mockLogger,
      output,
    );

    mockTeamFindAll.mockResolvedValue([]);

    const result = await useCase.execute({} as GetAllTeamsInput);

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();

    expect(output.present).toHaveBeenCalledTimes(1);
    const presented = output.present.mock.calls[0]?.[0] as GetAllTeamsResult;

    expect(presented).toEqual({
      teams: [],
      totalCount: 0,
    });
  });

  it('should return error when repository throws', async () => {
    const useCase = new GetAllTeamsUseCase(
      mockTeamRepo,
      mockTeamMembershipRepo,
      mockTeamStatsRepo,
      mockResultRepo,
      mockLogger,
      output,
    );

    const error = new Error('Repository error');
    mockTeamFindAll.mockRejectedValue(error);

    const result = await useCase.execute({} as GetAllTeamsInput);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();

    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details.message).toBe('Repository error');

    expect(output.present).not.toHaveBeenCalled();
  });
});
