import { type IPropertyRepository } from '@modules/listings/domain/repositories/property.repository';
import { type IMediaStorageService } from '@modules/listings/infrastructure/services/media-storage.service';
import { UploadMediaCommand } from '../commands/upload-media/upload-media.command';
import { UploadMediaHandler } from '../commands/upload-media/upload-media.handler';

describe('UploadMediaHandler', () => {
  let handler: UploadMediaHandler;
  let mockPropertyRepo: { [K in keyof IPropertyRepository]: ReturnType<typeof vi.fn> };
  let mockMediaStorage: { [K in keyof IMediaStorageService]: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockPropertyRepo = {
      findById: vi.fn(),
      save: vi.fn(),
      update: vi.fn(),
      addMedia: vi.fn().mockResolvedValue(undefined),
      findMediaByPropertyId: vi.fn(),
      deleteMedia: vi.fn(),
      countMediaByPropertyId: vi.fn(),
    };

    mockMediaStorage = {
      upload: vi.fn().mockResolvedValue('http://storage.local/media/test.jpg'),
      delete: vi.fn(),
    };

    handler = new UploadMediaHandler(
      mockPropertyRepo as any,
      mockMediaStorage as any,
    );
  });

  it('uploads image media successfully', async () => {
    mockPropertyRepo.findById.mockResolvedValue({ id: 'prop-1' });
    mockPropertyRepo.countMediaByPropertyId.mockResolvedValue(5);

    const command = new UploadMediaCommand('prop-1', 'user-1', {
      buffer: Buffer.from('fake-image'),
      mimetype: 'image/jpeg',
      originalname: 'photo.jpg',
      size: 1024,
    }, 'Phòng khách');

    const result = await handler.execute(command);

    expect(result.mediaId).toBeDefined();
    expect(result.url).toBe('http://storage.local/media/test.jpg');
    expect(mockMediaStorage.upload).toHaveBeenCalledWith(
      expect.any(Buffer), 'photo.jpg', 'image/jpeg', 'properties/prop-1',
    );
    expect(mockPropertyRepo.addMedia).toHaveBeenCalledTimes(1);
  });

  it('uploads video media with correct type', async () => {
    mockPropertyRepo.findById.mockResolvedValue({ id: 'prop-1' });
    mockPropertyRepo.countMediaByPropertyId.mockResolvedValue(0);
    mockMediaStorage.upload.mockResolvedValue('http://storage.local/media/video.mp4');

    const command = new UploadMediaCommand('prop-1', 'user-1', {
      buffer: Buffer.from('fake-video'),
      mimetype: 'video/mp4',
      originalname: 'tour.mp4',
      size: 10240,
    });

    const result = await handler.execute(command);

    expect(result.mediaId).toBeDefined();
    expect(result.url).toBe('http://storage.local/media/video.mp4');
  });

  it('throws NotFoundException when property does not exist', async () => {
    mockPropertyRepo.findById.mockResolvedValue(null);

    const command = new UploadMediaCommand('nonexistent', 'user-1', {
      buffer: Buffer.from('data'),
      mimetype: 'image/png',
      originalname: 'pic.png',
      size: 512,
    });

    await expect(handler.execute(command)).rejects.toThrow('Property');
  });

  it('throws ValidationException when media limit exceeded', async () => {
    mockPropertyRepo.findById.mockResolvedValue({ id: 'prop-1' });
    mockPropertyRepo.countMediaByPropertyId.mockResolvedValue(20);

    const command = new UploadMediaCommand('prop-1', 'user-1', {
      buffer: Buffer.from('data'),
      mimetype: 'image/jpeg',
      originalname: 'pic.jpg',
      size: 512,
    });

    await expect(handler.execute(command)).rejects.toThrow(/20/);
  });
});
