import { describe, it, expect, vi, type Mock } from 'vitest';
import {
  DeleteMediaUseCase,
  type DeleteMediaInput,
  type DeleteMediaResult,
  type DeleteMediaErrorCode,
} from './DeleteMediaUseCase';
import type { IMediaRepository } from '../../domain/repositories/IMediaRepository';
import type { MediaStoragePort } from '../ports/MediaStoragePort';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Media } from '../../domain/entities/Media';

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

describe('DeleteMediaUseCase', () => {
  let mediaRepo: {
    findById: Mock;
    delete: Mock;
  };
  let mediaStorage: {
    deleteMedia: Mock;
  };
  let logger: Logger;
  let output: TestOutputPort;
  let useCase: DeleteMediaUseCase;

  beforeEach(() => {
    mediaRepo = {
      findById: vi.fn(),
      delete: vi.fn(),
    };

    mediaStorage = {
      deleteMedia: vi.fn(),
    };

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

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

    useCase = new DeleteMediaUseCase(
      mediaRepo as unknown as IMediaRepository,
      mediaStorage as unknown as MediaStoragePort,
      output,
      logger,
    );
  });

  it('returns MEDIA_NOT_FOUND when media is not found', async () => {
    mediaRepo.findById.mockResolvedValue(null);

    const input: DeleteMediaInput = { mediaId: 'missing' };
    const result = await useCase.execute(input);

    expect(mediaRepo.findById).toHaveBeenCalledWith('missing');
    expect(result).toBeInstanceOf(Result);
    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      DeleteMediaErrorCode,
      { message: string }
    >;
    expect(err.code).toBe('MEDIA_NOT_FOUND');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('deletes media from storage and repository on success', async () => {
    const media = Media.create({
      id: 'media-1',
      filename: 'file.png',
      originalName: 'file.png',
      mimeType: 'image/png',
      size: 123,
      url: 'https://example.com/file.png',
      type: 'image',
      uploadedBy: 'user-1',
    });

    mediaRepo.findById.mockResolvedValue(media);

    const input: DeleteMediaInput = { mediaId: 'media-1' };
    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(mediaRepo.findById).toHaveBeenCalledWith('media-1');
    expect(mediaStorage.deleteMedia).toHaveBeenCalledWith(media.url.value);
    expect(mediaRepo.delete).toHaveBeenCalledWith('media-1');
    expect(output.present).toHaveBeenCalledWith({
      mediaId: 'media-1',
      deleted: true,
    });
  });

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

    const input: DeleteMediaInput = { mediaId: 'media-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<
      DeleteMediaErrorCode,
      { message: string }
    >;
    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(output.present).not.toHaveBeenCalled();
  });
});
