import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { once } from 'lodash';
import waitForPromises from 'helpers/wait_for_promises';
import Image from '~/content_editor/extensions/image';
import httpStatus from '~/lib/utils/http_status';
import { loadMarkdownApiResult } from '../markdown_processing_examples';
import { createTestEditor, createDocBuilder } from '../test_utils';

describe('content_editor/extensions/image', () => {
  let tiptapEditor;
  let eq;
  let doc;
  let p;
  let image;
  let renderMarkdown;
  let mock;
  const uploadsPath = '/uploads/';
  const validFile = new File(['foo'], 'foo.png', { type: 'image/png' });
  const invalidFile = new File(['foo'], 'bar.html', { type: 'text/html' });

  beforeEach(() => {
    renderMarkdown = jest
      .fn()
      .mockResolvedValue(loadMarkdownApiResult('project_wiki_attachment_image').body);

    tiptapEditor = createTestEditor({
      extensions: [Image.configure({ renderMarkdown, uploadsPath })],
    });

    ({
      builders: { doc, p, image },
      eq,
    } = createDocBuilder({
      tiptapEditor,
      names: { image: { nodeType: Image.name } },
    }));

    mock = new MockAdapter(axios);
  });

  afterEach(() => {
    mock.reset();
  });

  it.each`
    file           | valid    | description
    ${validFile}   | ${true}  | ${'handles paste event when mime type is valid'}
    ${invalidFile} | ${false} | ${'does not handle paste event when mime type is invalid'}
  `('$description', ({ file, valid }) => {
    const pasteEvent = Object.assign(new Event('paste'), {
      clipboardData: {
        files: [file],
      },
    });
    let handled;

    tiptapEditor.view.someProp('handlePaste', (eventHandler) => {
      handled = eventHandler(tiptapEditor.view, pasteEvent);
    });

    expect(handled).toBe(valid);
  });

  it.each`
    file           | valid    | description
    ${validFile}   | ${true}  | ${'handles drop event when mime type is valid'}
    ${invalidFile} | ${false} | ${'does not handle drop event when mime type is invalid'}
  `('$description', ({ file, valid }) => {
    const dropEvent = Object.assign(new Event('drop'), {
      dataTransfer: {
        files: [file],
      },
    });
    let handled;

    tiptapEditor.view.someProp('handleDrop', (eventHandler) => {
      handled = eventHandler(tiptapEditor.view, dropEvent);
    });

    expect(handled).toBe(valid);
  });

  it('handles paste event when mime type is correct', () => {
    const pasteEvent = Object.assign(new Event('paste'), {
      clipboardData: {
        files: [new File(['foo'], 'foo.png', { type: 'image/png' })],
      },
    });
    const handled = tiptapEditor.view.someProp('handlePaste', (eventHandler) => {
      return eventHandler(tiptapEditor.view, pasteEvent);
    });

    expect(handled).toBe(true);
  });

  describe('uploadImage command', () => {
    describe('when file has correct mime type', () => {
      let initialDoc;
      const base64EncodedFile = 'data:image/png;base64,Zm9v';

      beforeEach(() => {
        initialDoc = doc(p(''));
        tiptapEditor.commands.setContent(initialDoc.toJSON());
      });

      describe('when uploading image succeeds', () => {
        const successResponse = {
          link: {
            markdown: '[image](/uploads/25265/image.png)',
          },
        };

        beforeEach(() => {
          mock.onPost().reply(httpStatus.OK, successResponse);
        });

        it('inserts an image with src set to the encoded image file and uploading true', (done) => {
          const expectedDoc = doc(p(image({ uploading: true, src: base64EncodedFile })));

          tiptapEditor.on(
            'update',
            once(() => {
              expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
              done();
            }),
          );

          tiptapEditor.commands.uploadImage({ file: validFile });
        });

        it('updates the inserted image with canonicalSrc when upload is successful', async () => {
          const expectedDoc = doc(
            p(
              image({
                canonicalSrc: 'test-file.png',
                src: base64EncodedFile,
                alt: 'test file',
                uploading: false,
              }),
            ),
          );

          tiptapEditor.commands.uploadImage({ file: validFile });

          await waitForPromises();

          expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
        });
      });

      describe('when uploading image request fails', () => {
        beforeEach(() => {
          mock.onPost().reply(httpStatus.INTERNAL_SERVER_ERROR);
        });

        it('resets the doc to orginal state', async () => {
          const expectedDoc = doc(p(''));

          tiptapEditor.commands.uploadImage({ file: validFile });

          await waitForPromises();

          expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true);
        });

        it('emits an error event that includes an error message', (done) => {
          tiptapEditor.commands.uploadImage({ file: validFile });

          tiptapEditor.on('error', (message) => {
            expect(message).toBe('An error occurred while uploading the image. Please try again.');
            done();
          });
        });
      });
    });

    describe('when file does not have correct mime type', () => {
      let initialDoc;

      beforeEach(() => {
        initialDoc = doc(p(''));
        tiptapEditor.commands.setContent(initialDoc.toJSON());
      });

      it('does not start the upload image process', () => {
        tiptapEditor.commands.uploadImage({ file: invalidFile });

        expect(eq(tiptapEditor.state.doc, initialDoc)).toBe(true);
      });
    });
  });
});
