import React from 'react';
import { fireEvent, createEvent } from '@testing-library/react';
import renderWithTheme from '../../../../testUtils/renderWithTheme';

import FileDnD from '..';

const fireDropFileEvent = (
  element: HTMLElement,
  files: {
    name: string;
    size: number;
    type: string;
  }[]
): void => {
  const mockFiles = files.map(file => {
    const mockFile = new File([file.name], file.name, {
      type: file.type,
    });
    Object.defineProperty(mockFile, 'size', {
      value: file.size,
    });
    return mockFile;
  });

  const fileDropEvent = createEvent.drop(element, {
    dataTransfer: { files: mockFiles },
  });

  fireEvent(element, fileDropEvent);
};

describe('rendering', () => {
  it('shows dropzone content', () => {
    const { getByText } = renderWithTheme(
      <FileDnD
        text="Drap and Drop files here"
        description="Please only use these following file formats: .JPG, .JPEG, .PNG"
      />
    );
    expect(getByText('Drap and Drop files here')).toBeInTheDocument();
    expect(
      getByText(
        'Please only use these following file formats: .JPG, .JPEG, .PNG'
      )
    ).toBeInTheDocument();
  });
});

describe('interaction', () => {
  describe('dropping valid files', () => {
    it('allows to drop valid files into the dropzone', () => {
      const onAccept = jest.fn();
      const onReject = jest.fn();
      const { getByText } = renderWithTheme(
        <FileDnD
          text="Drap and Drop files here"
          description="Please only use these following file formats: .JPG, .JPEG, .PNG"
          onAccept={onAccept}
          onReject={onReject}
        />
      );
      fireDropFileEvent(getByText('Drap and Drop files here'), [
        {
          name: 'test.png',
          type: 'image/png',
          size: 100 * 1024,
        },
      ]);
      expect(onAccept).toHaveBeenCalledWith([expect.any(File)]);
      expect(onReject).not.toHaveBeenCalled();
    });

    it('allows to drop MULTIPLE valid files into the dropzone', () => {
      const onAccept = jest.fn();
      const onReject = jest.fn();
      const { getByText } = renderWithTheme(
        <FileDnD
          text="Drap and Drop files here"
          description="Please only use these following file formats: .JPG, .JPEG, .PNG"
          onAccept={onAccept}
          onReject={onReject}
          multiple
        />
      );
      fireDropFileEvent(getByText('Drap and Drop files here'), [
        {
          name: 'test1.png',
          type: 'image/jpg',
          size: 100 * 1024,
        },
        {
          name: 'test2.png',
          type: 'image/jpg',
          size: 200 * 1024,
        },
      ]);
      expect(onAccept).toHaveBeenCalledWith([
        expect.any(File),
        expect.any(File),
      ]);
      expect(onReject).not.toHaveBeenCalled();
    });

    it('selects ONE file when dropping multiple files into single dropzone file input', () => {
      const onAccept = jest.fn();
      const onReject = jest.fn();
      const { getByText } = renderWithTheme(
        <FileDnD
          text="Drap and Drop files here"
          description="Please only use these following file formats: .JPG, .JPEG, .PNG"
          onAccept={onAccept}
          onReject={onReject}
          multiple={false}
        />
      );
      fireDropFileEvent(getByText('Drap and Drop files here'), [
        {
          name: 'test1.png',
          type: 'image/jpg',
          size: 100 * 1024,
        },
        {
          name: 'test2.png',
          type: 'image/jpg',
          size: 200 * 1024,
        },
      ]);
      expect(onAccept).toHaveBeenCalledWith([expect.any(File)]);
      expect(onReject).not.toHaveBeenCalled();
    });
  });

  describe('dropping invalid files', () => {
    it('DOES NOT allow to drop invalid file type into the dropzone', () => {
      const onAccept = jest.fn();
      const onReject = jest.fn();
      const { getByText } = renderWithTheme(
        <FileDnD
          text="Drap and Drop files here"
          description="Please only use these following file formats: .JPG, .JPEG, .PNG"
          accept="image/*"
          onAccept={onAccept}
          onReject={onReject}
        />
      );
      fireDropFileEvent(getByText('Drap and Drop files here'), [
        {
          name: 'test.gzip',
          type: 'application/gzip',
          size: 100 * 1024,
        },
      ]);
      expect(onReject).toHaveBeenCalledWith([
        expect.objectContaining({
          file: expect.any(File),
          reason: 'format-not-allowed',
        }),
      ]);
      expect(onAccept).not.toHaveBeenCalled();
    });

    it('DOES NOT allow to drop invalid file size into the dropzone', () => {
      const onAccept = jest.fn();
      const onReject = jest.fn();
      const { getByText } = renderWithTheme(
        <FileDnD
          text="Drap and Drop files here"
          description="Please only use these following file formats: .JPG, .JPEG, .PNG"
          accept="image/*"
          onAccept={onAccept}
          onReject={onReject}
          maxSize={1024}
        />
      );
      fireDropFileEvent(getByText('Drap and Drop files here'), [
        {
          name: 'test.png',
          type: 'image/png',
          size: 1025,
        },
      ]);
      expect(onReject).toHaveBeenCalledWith([
        expect.objectContaining({
          file: expect.any(File),
          reason: 'size-limit-exceeded',
        }),
      ]);
      expect(onAccept).not.toHaveBeenCalled();
    });
  });

  describe('dropping mixing invalid & valid files', () => {
    it('allows to drop valid files but NOT invalid ones type into the dropzone', () => {
      const onAccept = jest.fn();
      const onReject = jest.fn();
      const { getByText } = renderWithTheme(
        <FileDnD
          text="Drap and Drop files here"
          description="Please only use these following file formats: .JPG, .JPEG, .PNG"
          accept="image/*"
          onAccept={onAccept}
          onReject={onReject}
          multiple
          maxSize={1024}
        />
      );
      fireDropFileEvent(getByText('Drap and Drop files here'), [
        {
          name: 'test.gzip',
          type: 'application/gzip',
          size: 1024,
        },
        {
          name: 'test.jpg',
          type: 'image/jpg',
          size: 1024,
        },
        {
          name: 'test.gzip',
          type: 'application/gzip',
          size: 1025,
        },
        {
          name: 'test.png',
          type: 'image/png',
          size: 1025,
        },
      ]);
      expect(onReject).toHaveBeenCalledWith([
        expect.objectContaining({
          file: expect.any(File),
          reason: 'format-not-allowed',
        }),
        expect.objectContaining({
          file: expect.any(File),
          reason: 'format-not-allowed',
        }),
        expect.objectContaining({
          file: expect.any(File),
          reason: 'size-limit-exceeded',
        }),
      ]);
      expect(onAccept).toHaveBeenCalledWith([expect.any(File)]);
    });
  });
});
