import React from 'react';
import fs from 'fs';
import path from 'path';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import AvatarUpload from '../AvatarUpload';
import http from '@/http';
import { resizeImage, MAX_FILE_SIZE } from '@/utils/imageResize';

jest.mock('@/utils/imageResize', () => ({
  ...jest.requireActual('@/utils/imageResize'),
  resizeImage: jest.fn(),
}));

const mockedResizeImage = resizeImage as jest.MockedFunction<typeof resizeImage>;
const mockedHttp = http as jest.Mocked<typeof http>;

const PUBLIC_DIR = path.join(__dirname, '../../../../public');

function fileOfSize(bytes: number): File {
  const file = new File(['x'], 'avatar.jpg', { type: 'image/jpeg' });
  Object.defineProperty(file, 'size', { value: bytes });
  return file;
}

function selectFile(container: HTMLElement, file: File): void {
  const input = container.querySelector('input[type="file"]') as HTMLInputElement;
  fireEvent.change(input, { target: { files: [file] } });
}

describe('AvatarUpload', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockedResizeImage.mockResolvedValue(new Blob(['resized'], { type: 'image/jpeg' }));
  });

  it('shouldShowDefaultAvatarWhenUserHasNone', () => {
    render(<AvatarUpload currentAvatarUrl={null} onAvatarChange={jest.fn()} />);

    expect(screen.getByAltText('Avatar')).toHaveAttribute(
      'src',
      '/images/tk-default-avatar.svg'
    );
  });

  it('shouldPointToAnExistingDefaultAvatarFile', () => {
    render(<AvatarUpload currentAvatarUrl={null} onAvatarChange={jest.fn()} />);

    const src = screen.getByAltText('Avatar').getAttribute('src') as string;

    expect(fs.existsSync(path.join(PUBLIC_DIR, src))).toBe(true);
  });

  it('shouldHideDeleteButtonWhenUserHasNoAvatar', () => {
    render(<AvatarUpload currentAvatarUrl={null} onAvatarChange={jest.fn()} />);

    expect(screen.queryByRole('button', { name: /avatar_delete/i })).not.toBeInTheDocument();
  });

  it('shouldShowDeleteButtonWhenUserHasAnAvatar', () => {
    render(
      <AvatarUpload
        currentAvatarUrl="https://cdn.example.com/avatar.jpg"
        onAvatarChange={jest.fn()}
      />
    );

    expect(screen.getByRole('button', { name: /avatar_delete/i })).toBeInTheDocument();
    expect(screen.getByAltText('Avatar')).toHaveAttribute(
      'src',
      'https://cdn.example.com/avatar.jpg'
    );
  });

  it('shouldRejectFilesLargerThanOneMegabyteWithoutCallingTheApi', async () => {
    const onAvatarChange = jest.fn();
    const { container } = render(
      <AvatarUpload currentAvatarUrl={null} onAvatarChange={onAvatarChange} />
    );

    selectFile(container, fileOfSize(MAX_FILE_SIZE + 1));

    expect(await screen.findByText('avatar_too_large')).toBeInTheDocument();
    expect(mockedHttp.post).not.toHaveBeenCalled();
    expect(mockedResizeImage).not.toHaveBeenCalled();
    expect(onAvatarChange).not.toHaveBeenCalled();
  });

  it('shouldResizeThenUploadAValidFileAndReportTheNewUrl', async () => {
    const onAvatarChange = jest.fn();
    mockedHttp.post.mockResolvedValueOnce({
      status: 200,
      data: { avatarUrl: 'https://cdn.example.com/new-avatar.jpg' },
    });

    const { container } = render(
      <AvatarUpload currentAvatarUrl={null} onAvatarChange={onAvatarChange} />
    );

    const file = fileOfSize(500 * 1024);
    selectFile(container, file);

    await waitFor(() => {
      expect(onAvatarChange).toHaveBeenCalledWith('https://cdn.example.com/new-avatar.jpg');
    });

    expect(mockedResizeImage).toHaveBeenCalledWith(file, 300, 300);
    expect(mockedHttp.post).toHaveBeenCalledTimes(1);

    const [url, body] = mockedHttp.post.mock.calls[0];
    expect(url).toBe('/me/avatar');
    expect(body).toBeInstanceOf(FormData);
    expect((body as FormData).get('avatar')).toBeInstanceOf(Blob);
  });

  it('shouldDeleteTheAvatarAndReportItIsGone', async () => {
    const onAvatarChange = jest.fn();
    render(
      <AvatarUpload
        currentAvatarUrl="https://cdn.example.com/avatar.jpg"
        onAvatarChange={onAvatarChange}
      />
    );

    fireEvent.click(screen.getByRole('button', { name: /avatar_delete/i }));

    await waitFor(() => {
      expect(onAvatarChange).toHaveBeenCalledWith(null);
    });

    expect(mockedHttp.delete).toHaveBeenCalledWith('/me/avatar');
  });
});
