import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import {
  GetCurrentUserSocialUseCase,
  type GetCurrentUserSocialApplicationError,
  type GetCurrentUserSocialInput,
  type GetCurrentUserSocialResult,
} from './GetCurrentUserSocialUseCase';
import type { ISocialGraphRepository } from '../../domain/repositories/ISocialGraphRepository';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import { Driver } from '@core/racing/domain/entities/Driver';

describe('GetCurrentUserSocialUseCase', () => {
  let socialGraphRepository: ISocialGraphRepository & { getFriends: Mock };
  let logger: Logger & { debug: Mock; info: Mock; warn: Mock; error: Mock };
  let output: UseCaseOutputPort<GetCurrentUserSocialResult> & { present: Mock };
  let useCase: GetCurrentUserSocialUseCase;

  beforeEach(() => {
    socialGraphRepository = {
      getFriends: vi.fn(),
      getFriendIds: vi.fn(),
      getSuggestedFriends: vi.fn(),
    } as unknown as ISocialGraphRepository & { getFriends: Mock };

    logger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    } as unknown as Logger & { debug: Mock; info: Mock; warn: Mock; error: Mock };

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

    useCase = new GetCurrentUserSocialUseCase(socialGraphRepository, logger, output);
  });

  it('presents current user social with mapped friends', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z'));

    const friends = [
      Driver.create({
        id: 'friend-1',
        iracingId: '123',
        name: 'Friend One',
        country: 'US',
      }),
    ];

    socialGraphRepository.getFriends.mockResolvedValue(friends);

    const input: GetCurrentUserSocialInput = { driverId: 'driver-1' };
    const result = await useCase.execute(input);

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

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

    expect(presented.currentUser).toEqual({
      driverId: 'driver-1',
      displayName: '',
      avatarUrl: '',
      countryCode: '',
    });

    expect(presented.friends).toHaveLength(1);
    expect(presented.friends[0]).toEqual({
      driverId: 'friend-1',
      displayName: 'Friend One',
      avatarUrl: '',
      countryCode: '',
      isOnline: false,
      lastSeen: new Date('2025-01-01T00:00:00.000Z'),
    });

    expect(logger.warn).not.toHaveBeenCalled();

    vi.useRealTimers();
  });

  it('warns and presents empty friends list when no friends exist', async () => {
    socialGraphRepository.getFriends.mockResolvedValue([]);

    const input: GetCurrentUserSocialInput = { driverId: 'driver-1' };
    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(output.present).toHaveBeenCalledTimes(1);

    const presented = (output.present as Mock).mock.calls[0]![0] as GetCurrentUserSocialResult;
    expect(presented.friends).toEqual([]);

    expect(logger.warn).toHaveBeenCalledTimes(1);
    expect((logger.warn as Mock).mock.calls[0]![0]).toBe(
      'GetCurrentUserSocialUseCase.execute: No friends found for driverId: driver-1',
    );
  });

  it('returns REPOSITORY_ERROR when repository throws', async () => {
    socialGraphRepository.getFriends.mockRejectedValue(new Error('DB error'));

    const input: GetCurrentUserSocialInput = { driverId: 'driver-1' };
    const result = await useCase.execute(input);

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

    const err = result.unwrapErr() as GetCurrentUserSocialApplicationError;
    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details.message).toBe('DB error');

    expect(output.present).not.toHaveBeenCalled();
    expect(logger.error).toHaveBeenCalledTimes(1);
  });
});