import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
  ApproveLeagueJoinRequestUseCase,
  type ApproveLeagueJoinRequestResult,
} from './ApproveLeagueJoinRequestUseCase';
import { League } from '../../domain/entities/League';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';

describe('ApproveLeagueJoinRequestUseCase', () => {
  let mockLeagueMembershipRepo: {
    getJoinRequests: Mock;
    removeJoinRequest: Mock;
    saveMembership: Mock;
    getLeagueMembers: Mock;
  };

  let mockLeagueRepo: {
    findById: Mock;
  };

  beforeEach(() => {
    mockLeagueMembershipRepo = {
      getJoinRequests: vi.fn(),
      removeJoinRequest: vi.fn(),
      saveMembership: vi.fn(),
      getLeagueMembers: vi.fn(),
    };

    mockLeagueRepo = {
      findById: vi.fn(),
    };
  });

  it('approve removes request and adds member', async () => {
    const output = {
      present: vi.fn(),
    };

    const useCase = new ApproveLeagueJoinRequestUseCase(
      mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository,
      mockLeagueRepo as unknown as ILeagueRepository,
    );

    const leagueId = 'league-1';
    const joinRequestId = 'req-1';
    const joinRequests = [{ id: joinRequestId, leagueId, driverId: 'driver-1', requestedAt: new Date(), message: 'msg' }];

    mockLeagueRepo.findById.mockResolvedValue(
      League.create({
        id: leagueId,
        name: 'L',
        description: 'D',
        ownerId: 'owner-1',
        settings: { maxDrivers: 32, visibility: 'unranked' },
        participantCount: 0,
      }),
    );

    mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([]);
    mockLeagueMembershipRepo.getJoinRequests.mockResolvedValue(joinRequests);

    const result = await useCase.execute(
      { leagueId, joinRequestId },
      output as unknown as UseCaseOutputPort<ApproveLeagueJoinRequestResult>,
    );

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();
    expect(mockLeagueMembershipRepo.removeJoinRequest).toHaveBeenCalledWith(joinRequestId);
    expect(mockLeagueMembershipRepo.saveMembership).toHaveBeenCalledTimes(1);

    const savedMembership = (mockLeagueMembershipRepo.saveMembership as Mock).mock.calls[0]?.[0] as unknown as {
      id: string;
      leagueId: { toString(): string };
      driverId: { toString(): string };
      role: { toString(): string };
      status: { toString(): string };
      joinedAt: { toDate(): Date };
    };

    expect(savedMembership.id).toEqual(expect.any(String));
    expect(savedMembership.leagueId.toString()).toBe('league-1');
    expect(savedMembership.driverId.toString()).toBe('driver-1');
    expect(savedMembership.role.toString()).toBe('member');
    expect(savedMembership.status.toString()).toBe('active');
    expect(savedMembership.joinedAt.toDate()).toBeInstanceOf(Date);

    expect(output.present).toHaveBeenCalledWith({ success: true, message: 'Join request approved.' });
  });

  it('approve returns error when request missing', async () => {
    const output = {
      present: vi.fn(),
    };

    const useCase = new ApproveLeagueJoinRequestUseCase(
      mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository,
      mockLeagueRepo as unknown as ILeagueRepository,
    );

    mockLeagueRepo.findById.mockResolvedValue(
      League.create({
        id: 'league-1',
        name: 'L',
        description: 'D',
        ownerId: 'owner-1',
        settings: { maxDrivers: 32, visibility: 'unranked' },
        participantCount: 0,
      }),
    );

    mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([]);
    mockLeagueMembershipRepo.getJoinRequests.mockResolvedValue([]);

    const result = await useCase.execute(
      { leagueId: 'league-1', joinRequestId: 'req-1' },
      output as unknown as UseCaseOutputPort<ApproveLeagueJoinRequestResult>,
    );

    expect(result.isOk()).toBe(false);
    expect(result.error!.code).toBe('JOIN_REQUEST_NOT_FOUND');
    expect(output.present).not.toHaveBeenCalled();
    expect(mockLeagueMembershipRepo.removeJoinRequest).not.toHaveBeenCalled();
    expect(mockLeagueMembershipRepo.saveMembership).not.toHaveBeenCalled();
  });

  it('rejects approval when league is at capacity and does not mutate state', async () => {
    const output = {
      present: vi.fn(),
    };

    const useCase = new ApproveLeagueJoinRequestUseCase(
      mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository,
      mockLeagueRepo as unknown as ILeagueRepository,
    );

    const leagueId = 'league-1';
    const joinRequestId = 'req-1';
    const joinRequests = [{ id: joinRequestId, leagueId, driverId: 'driver-2', requestedAt: new Date(), message: 'msg' }];

    mockLeagueRepo.findById.mockResolvedValue(
      League.create({
        id: leagueId,
        name: 'L',
        description: 'D',
        ownerId: 'owner-1',
        settings: { maxDrivers: 2, visibility: 'unranked' },
        participantCount: 2,
      }),
    );

    mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([
      {
        id: `${leagueId}:owner-1`,
        leagueId: { toString: () => leagueId },
        driverId: { toString: () => 'owner-1' },
        role: { toString: () => 'owner' },
        status: { toString: () => 'active' },
        joinedAt: { toDate: () => new Date() },
      },
      {
        id: `${leagueId}:driver-1`,
        leagueId: { toString: () => leagueId },
        driverId: { toString: () => 'driver-1' },
        role: { toString: () => 'member' },
        status: { toString: () => 'active' },
        joinedAt: { toDate: () => new Date() },
      },
    ]);

    mockLeagueMembershipRepo.getJoinRequests.mockResolvedValue(joinRequests);

    const result = await useCase.execute(
      { leagueId, joinRequestId },
      output as unknown as UseCaseOutputPort<ApproveLeagueJoinRequestResult>,
    );

    expect(result.isErr()).toBe(true);
    expect(result.unwrapErr().code).toBe('LEAGUE_AT_CAPACITY');
    expect(output.present).not.toHaveBeenCalled();
    expect(mockLeagueMembershipRepo.removeJoinRequest).not.toHaveBeenCalled();
    expect(mockLeagueMembershipRepo.saveMembership).not.toHaveBeenCalled();
  });
});