import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
  GetTeamMembershipUseCase,
  type GetTeamMembershipInput,
  type GetTeamMembershipResult,
  type GetTeamMembershipErrorCode,
} from './GetTeamMembershipUseCase';
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
import type { Logger } from '@core/shared/application';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';

describe('GetTeamMembershipUseCase', () => {
  const mockGetMembership = vi.fn();
  const mockMembershipRepo: ITeamMembershipRepository = {
    getMembership: mockGetMembership,
    getActiveMembershipForDriver: vi.fn(),
    getTeamMembers: vi.fn(),
    saveMembership: vi.fn(),
    removeMembership: vi.fn(),
    getJoinRequests: vi.fn(),
    countByTeamId: vi.fn(),
    saveJoinRequest: vi.fn(),
    removeJoinRequest: vi.fn(),
  };

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

  let output: UseCaseOutputPort<GetTeamMembershipResult> & { present: ReturnType<typeof vi.fn> };
  let useCase: GetTeamMembershipUseCase;

  beforeEach(() => {
    vi.clearAllMocks();

    output = { present: vi.fn() } as unknown as UseCaseOutputPort<GetTeamMembershipResult> & {
      present: ReturnType<typeof vi.fn>;
    };

    useCase = new GetTeamMembershipUseCase(mockMembershipRepo, mockLogger, output);
  });

  it('should present membership data when membership exists', async () => {
    const teamId = 'team1';
    const driverId = 'driver1';
    const membership = {
      teamId,
      driverId,
      role: 'manager' as const,
      status: 'active' as const,
      joinedAt: new Date('2023-01-01'),
    };

    mockGetMembership.mockResolvedValue(membership);

    const input: GetTeamMembershipInput = { teamId, driverId };

    const result = await useCase.execute(input);

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

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

    expect(presented.membership).toEqual({
      role: 'manager',
      joinedAt: '2023-01-01T00:00:00.000Z',
      isActive: true,
    });
  });

  it('should present null membership when no membership exists', async () => {
    const teamId = 'team1';
    const driverId = 'driver1';

    mockGetMembership.mockResolvedValue(null);

    const input: GetTeamMembershipInput = { teamId, driverId };

    const result = await useCase.execute(input);

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

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

    expect(presented.membership).toBeNull();
  });

  it('should map driver role to member', async () => {
    const teamId = 'team1';
    const driverId = 'driver1';
    const membership = {
      teamId,
      driverId,
      role: 'driver' as const,
      status: 'active' as const,
      joinedAt: new Date('2023-01-01'),
    };

    mockGetMembership.mockResolvedValue(membership);

    const input: GetTeamMembershipInput = { teamId, driverId };

    const result = await useCase.execute(input);

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

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

    expect(presented.membership?.role).toBe('member');
  });

  it('should return error when repository throws', async () => {
    const teamId = 'team1';
    const driverId = 'driver1';
    const error = new Error('Repository error');

    mockGetMembership.mockRejectedValue(error);

    const input: GetTeamMembershipInput = { teamId, driverId };

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);

    const err = result.unwrapErr() as ApplicationErrorCode<
      GetTeamMembershipErrorCode,
      { message: string }
    >;

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

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