import { describe, it, expect, vi, type Mock } from 'vitest';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import {
  RequestAvatarGenerationUseCase,
  type RequestAvatarGenerationInput,
  type RequestAvatarGenerationErrorCode,
  type RequestAvatarGenerationResult,
} from './RequestAvatarGenerationUseCase';
import type { IAvatarGenerationRepository } from '../../domain/repositories/IAvatarGenerationRepository';
import type { FaceValidationPort } from '../ports/FaceValidationPort';
import type { AvatarGenerationPort } from '../ports/AvatarGenerationPort';

vi.mock('uuid', () => ({
  v4: () => 'request-1',
}));

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

describe('RequestAvatarGenerationUseCase', () => {
  let avatarRepo: { save: Mock };
  let faceValidation: { validateFacePhoto: Mock };
  let avatarGeneration: { generateAvatars: Mock };
  let output: TestOutputPort;
  let logger: Logger;
  let useCase: RequestAvatarGenerationUseCase;

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

    faceValidation = {
      validateFacePhoto: vi.fn(),
    };

    avatarGeneration = {
      generateAvatars: vi.fn(),
    };

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

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

    useCase = new RequestAvatarGenerationUseCase(
      avatarRepo as unknown as IAvatarGenerationRepository,
      faceValidation as unknown as FaceValidationPort,
      avatarGeneration as unknown as AvatarGenerationPort,
      output,
      logger,
    );
  });

  it('completes generation and presents avatar URLs', async () => {
    faceValidation.validateFacePhoto.mockResolvedValue({
      isValid: true,
      hasFace: true,
      faceCount: 1,
    });

    avatarGeneration.generateAvatars.mockResolvedValue({
      success: true,
      avatars: [{ url: 'https://example.com/a.png' }, { url: 'https://example.com/b.png' }],
    });

    const input: RequestAvatarGenerationInput = {
      userId: 'user-1',
      facePhotoData: 'data:image/png;base64,abc',
      suitColor: 'red' as unknown as RequestAvatarGenerationInput['suitColor'],
      style: 'cartoon',
    };

    const result = await useCase.execute(input);

    expect(result).toBeInstanceOf(Result);
    expect(result.isOk()).toBe(true);

    expect(faceValidation.validateFacePhoto).toHaveBeenCalledWith(input.facePhotoData);
    expect(avatarGeneration.generateAvatars).toHaveBeenCalled();
    expect(avatarRepo.save).toHaveBeenCalledTimes(4);

    expect(output.present).toHaveBeenCalledWith({
      requestId: 'request-1',
      status: 'completed',
      avatarUrls: ['https://example.com/a.png', 'https://example.com/b.png'],
    });
  });

  it('returns FACE_VALIDATION_FAILED when face validation fails', async () => {
    faceValidation.validateFacePhoto.mockResolvedValue({
      isValid: false,
      hasFace: true,
      faceCount: 1,
      errorMessage: 'Bad image',
    });

    const input: RequestAvatarGenerationInput = {
      userId: 'user-1',
      facePhotoData: 'data:image/png;base64,abc',
      suitColor: 'red' as unknown as RequestAvatarGenerationInput['suitColor'],
    };

    const result: Result<void, ApplicationErrorCode<RequestAvatarGenerationErrorCode, { message: string }>> =
      await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('FACE_VALIDATION_FAILED');
    expect(err.details?.message).toBe('Bad image');

    expect(avatarGeneration.generateAvatars).not.toHaveBeenCalled();
    expect(output.present).not.toHaveBeenCalled();
    expect(avatarRepo.save).toHaveBeenCalledTimes(3);
  });

  it('returns GENERATION_FAILED when avatar generation fails', async () => {
    faceValidation.validateFacePhoto.mockResolvedValue({
      isValid: true,
      hasFace: true,
      faceCount: 1,
    });

    avatarGeneration.generateAvatars.mockResolvedValue({
      success: false,
      errorMessage: 'Generation service down',
      avatars: [],
    });

    const input: RequestAvatarGenerationInput = {
      userId: 'user-1',
      facePhotoData: 'data:image/png;base64,abc',
      suitColor: 'red' as unknown as RequestAvatarGenerationInput['suitColor'],
    };

    const result: Result<void, ApplicationErrorCode<RequestAvatarGenerationErrorCode, { message: string }>> =
      await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('GENERATION_FAILED');
    expect(err.details?.message).toBe('Generation service down');

    expect(output.present).not.toHaveBeenCalled();
    expect(avatarRepo.save).toHaveBeenCalledTimes(4);
  });

  it('returns REPOSITORY_ERROR when repository throws', async () => {
    avatarRepo.save.mockRejectedValueOnce(new Error('DB error'));

    const input: RequestAvatarGenerationInput = {
      userId: 'user-1',
      facePhotoData: 'data:image/png;base64,abc',
      suitColor: 'red' as unknown as RequestAvatarGenerationInput['suitColor'],
    };

    const result: Result<void, ApplicationErrorCode<RequestAvatarGenerationErrorCode, { message: string }>> =
      await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details?.message).toBe('DB error');

    expect(output.present).not.toHaveBeenCalled();
    expect((logger.error as unknown as Mock)).toHaveBeenCalled();
  });
});