import { describe, it, expect, vi, type Mock } from 'vitest';
import {
  GetAvatarUseCase,
  type GetAvatarInput,
  type GetAvatarResult,
  type GetAvatarErrorCode,
} from './GetAvatarUseCase';
import type { IAvatarRepository } from '../../domain/repositories/IAvatarRepository';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Avatar } from '../../domain/entities/Avatar';

interface TestOutputPort extends UseCaseOutputPort<GetAvatarResult> {
  present: Mock;
  result?: GetAvatarResult;
}

describe('GetAvatarUseCase', () => {
  let avatarRepo: {
    findActiveByDriverId: Mock;
    save: Mock;
  };
  let logger: Logger;
  let output: TestOutputPort;
  let useCase: GetAvatarUseCase;

  beforeEach(() => {
    avatarRepo = {
      findActiveByDriverId: vi.fn(),
      save: vi.fn(),
    };

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

    output = {
      present: vi.fn((result: GetAvatarResult) => {
        output.result = result;
      }),
    } as unknown as TestOutputPort;

    useCase = new GetAvatarUseCase(
      avatarRepo as unknown as IAvatarRepository,
      output,
      logger,
    );
  });

  it('returns AVATAR_NOT_FOUND when no avatar exists for driver', async () => {
    avatarRepo.findActiveByDriverId.mockResolvedValue(null);

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

    expect(avatarRepo.findActiveByDriverId).toHaveBeenCalledWith('driver-1');
    expect(result).toBeInstanceOf(Result);
    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      GetAvatarErrorCode,
      { message: string }
    >;
    expect(err.code).toBe('AVATAR_NOT_FOUND');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('presents avatar details when avatar exists', async () => {
    const avatar = Avatar.create({
      id: 'avatar-1',
      driverId: 'driver-1',
      mediaUrl: 'https://example.com/avatar.png',
    });

    avatarRepo.findActiveByDriverId.mockResolvedValue(avatar);

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

    expect(result.isOk()).toBe(true);
    expect(avatarRepo.findActiveByDriverId).toHaveBeenCalledWith('driver-1');
    expect(output.present).toHaveBeenCalledWith({
      avatar: {
        id: avatar.id,
        driverId: avatar.driverId,
        mediaUrl: avatar.mediaUrl.value,
        selectedAt: avatar.selectedAt,
      },
    });
  });

  it('handles repository errors by returning REPOSITORY_ERROR', async () => {
    avatarRepo.findActiveByDriverId.mockRejectedValue(new Error('DB error'));

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

    expect((logger.error as unknown as Mock)).toHaveBeenCalled();
    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      GetAvatarErrorCode,
      { message: string }
    >;
    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(output.present).not.toHaveBeenCalled();
  });
});
