import { GlModal, GlFormTextarea, GlToggle } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import httpStatusCodes from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
import NewDirectoryModal from '~/repository/components/new_directory_modal.vue';

jest.mock('~/flash');
jest.mock('~/lib/utils/url_utility', () => ({
  visitUrl: jest.fn(),
}));

const initialProps = {
  modalTitle: 'Create New Directory',
  modalId: 'modal-new-directory',
  commitMessage: 'Add new directory',
  targetBranch: 'some-target-branch',
  originalBranch: 'master',
  canPushCode: true,
  path: 'create_dir',
};

const defaultFormValue = {
  dirName: 'foo',
  originalBranch: initialProps.originalBranch,
  branchName: initialProps.targetBranch,
  commitMessage: initialProps.commitMessage,
  createNewMr: true,
};

describe('NewDirectoryModal', () => {
  let wrapper;
  let mock;

  const createComponent = (props = {}) => {
    wrapper = shallowMount(NewDirectoryModal, {
      propsData: {
        ...initialProps,
        ...props,
      },
      attrs: {
        static: true,
        visible: true,
      },
    });
  };

  const findModal = () => wrapper.findComponent(GlModal);
  const findDirName = () => wrapper.find('[name="dir_name"]');
  const findBranchName = () => wrapper.find('[name="branch_name"]');
  const findCommitMessage = () => wrapper.findComponent(GlFormTextarea);
  const findMrToggle = () => wrapper.findComponent(GlToggle);

  const fillForm = async (inputValue = {}) => {
    const {
      dirName = defaultFormValue.dirName,
      branchName = defaultFormValue.branchName,
      commitMessage = defaultFormValue.commitMessage,
      createNewMr = true,
    } = inputValue;

    await findDirName().vm.$emit('input', dirName);
    await findBranchName().vm.$emit('input', branchName);
    await findCommitMessage().vm.$emit('input', commitMessage);
    await findMrToggle().vm.$emit('change', createNewMr);
    await nextTick;
  };

  const submitForm = async () => {
    const mockEvent = { preventDefault: jest.fn() };
    findModal().vm.$emit('primary', mockEvent);
    await waitForPromises();
  };

  afterEach(() => {
    wrapper.destroy();
  });

  it('renders modal component', () => {
    createComponent();

    const { modalTitle: title } = initialProps;

    expect(findModal().props()).toMatchObject({
      title,
      size: 'md',
      actionPrimary: {
        text: NewDirectoryModal.i18n.PRIMARY_OPTIONS_TEXT,
      },
      actionCancel: {
        text: 'Cancel',
      },
    });
  });

  describe('form', () => {
    it.each`
      component            | defaultValue                  | canPushCode | targetBranch                 | originalBranch                 | exist
      ${findDirName}       | ${undefined}                  | ${true}     | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${true}
      ${findBranchName}    | ${initialProps.targetBranch}  | ${true}     | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${true}
      ${findBranchName}    | ${undefined}                  | ${false}    | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${false}
      ${findCommitMessage} | ${initialProps.commitMessage} | ${true}     | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${true}
      ${findMrToggle}      | ${'true'}                     | ${true}     | ${'new-target-branch'}       | ${'master'}                    | ${true}
      ${findMrToggle}      | ${'true'}                     | ${true}     | ${'master'}                  | ${'master'}                    | ${true}
    `(
      'has the correct form fields ',
      ({ component, defaultValue, canPushCode, targetBranch, originalBranch, exist }) => {
        createComponent({
          canPushCode,
          targetBranch,
          originalBranch,
        });
        const formField = component();

        if (!exist) {
          expect(formField.exists()).toBe(false);
          return;
        }

        expect(formField.exists()).toBe(true);
        expect(formField.attributes('value')).toBe(defaultValue);
      },
    );
  });

  describe('form submission', () => {
    beforeEach(async () => {
      mock = new MockAdapter(axios);
    });

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

    describe('valid form', () => {
      beforeEach(() => {
        createComponent();
      });

      it('passes the formData', async () => {
        const {
          dirName,
          branchName,
          commitMessage,
          originalBranch,
          createNewMr,
        } = defaultFormValue;
        mock.onPost(initialProps.path).reply(httpStatusCodes.OK, {});
        await fillForm();
        await submitForm();

        expect(mock.history.post[0].data.get('dir_name')).toEqual(dirName);
        expect(mock.history.post[0].data.get('branch_name')).toEqual(branchName);
        expect(mock.history.post[0].data.get('commit_message')).toEqual(commitMessage);
        expect(mock.history.post[0].data.get('original_branch')).toEqual(originalBranch);
        expect(mock.history.post[0].data.get('create_merge_request')).toEqual(String(createNewMr));
      });

      it('does not submit "create_merge_request" formData if createNewMr is not checked', async () => {
        mock.onPost(initialProps.path).reply(httpStatusCodes.OK, {});
        await fillForm({ createNewMr: false });
        await submitForm();
        expect(mock.history.post[0].data.get('create_merge_request')).toBeNull();
      });

      it('redirects to the new directory', async () => {
        const response = { filePath: 'new-dir-path' };
        mock.onPost(initialProps.path).reply(httpStatusCodes.OK, response);

        await fillForm({ dirName: 'foo', branchName: 'master', commitMessage: 'foo' });
        await submitForm();

        expect(visitUrl).toHaveBeenCalledWith(response.filePath);
      });
    });

    describe('invalid form', () => {
      beforeEach(() => {
        createComponent();
      });

      it('disables submit button', async () => {
        await fillForm({ dirName: '', branchName: '', commitMessage: '' });
        expect(findModal().props('actionPrimary').attributes[0].disabled).toBe(true);
      });

      it('creates a flash error', async () => {
        mock.onPost(initialProps.path).timeout();

        await fillForm({ dirName: 'foo', branchName: 'master', commitMessage: 'foo' });
        await submitForm();

        expect(createFlash).toHaveBeenCalledWith({
          message: NewDirectoryModal.i18n.ERROR_MESSAGE,
        });
      });
    });
  });
});
