import { shallowMount, createLocalVue } from '@vue/test-utils';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import Home from '~/static_site_editor/pages/home.vue';
import SkeletonLoader from '~/static_site_editor/components/skeleton_loader.vue';
import EditArea from '~/static_site_editor/components/edit_area.vue';
import EditMetaModal from '~/static_site_editor/components/edit_meta_modal.vue';
import InvalidContentMessage from '~/static_site_editor/components/invalid_content_message.vue';
import SubmitChangesError from '~/static_site_editor/components/submit_changes_error.vue';
import submitContentChangesMutation from '~/static_site_editor/graphql/mutations/submit_content_changes.mutation.graphql';
import hasSubmittedChangesMutation from '~/static_site_editor/graphql/mutations/has_submitted_changes.mutation.graphql';
import { SUCCESS_ROUTE } from '~/static_site_editor/router/constants';
import { TRACKING_ACTION_INITIALIZE_EDITOR } from '~/static_site_editor/constants';

import {
  project,
  returnUrl,
  sourceContentYAML as content,
  sourceContentTitle as title,
  sourcePath,
  username,
  mergeRequestMeta,
  savedContentMeta,
  submitChangesError,
  trackingCategory,
  images,
  mounts,
  branch,
  baseUrl,
  imageRoot,
} from '../mock_data';

const localVue = createLocalVue();

describe('static_site_editor/pages/home', () => {
  let wrapper;
  let store;
  let $apollo;
  let $router;
  let mutateMock;
  let trackingSpy;
  const defaultAppData = {
    isSupportedContent: true,
    hasSubmittedChanges: false,
    returnUrl,
    project,
    username,
    sourcePath,
    mounts,
    branch,
    baseUrl,
    imageUploadPath: imageRoot,
  };
  const hasSubmittedChangesMutationPayload = {
    data: {
      appData: { ...defaultAppData, hasSubmittedChanges: true },
    },
  };

  const buildApollo = (queries = {}) => {
    mutateMock = jest.fn();

    $apollo = {
      queries: {
        sourceContent: {
          loading: false,
        },
        ...queries,
      },
      mutate: mutateMock,
    };
  };

  const buildRouter = () => {
    $router = {
      push: jest.fn(),
    };
  };

  const buildWrapper = (data = {}) => {
    wrapper = shallowMount(Home, {
      localVue,
      store,
      mocks: {
        $apollo,
        $router,
      },
      data() {
        return {
          appData: { ...defaultAppData },
          sourceContent: { title, content },
          ...data,
        };
      },
    });
  };

  const findEditArea = () => wrapper.find(EditArea);
  const findEditMetaModal = () => wrapper.find(EditMetaModal);
  const findInvalidContentMessage = () => wrapper.find(InvalidContentMessage);
  const findSkeletonLoader = () => wrapper.find(SkeletonLoader);
  const findSubmitChangesError = () => wrapper.find(SubmitChangesError);

  beforeEach(() => {
    buildApollo();
    buildRouter();

    document.body.dataset.page = trackingCategory;
    trackingSpy = mockTracking(document.body.dataset.page, undefined, jest.spyOn);
  });

  afterEach(() => {
    wrapper.destroy();
    unmockTracking();
    wrapper = null;
    $apollo = null;
  });

  describe('when content is loaded', () => {
    beforeEach(() => {
      buildWrapper();
    });

    it('renders edit area', () => {
      expect(findEditArea().exists()).toBe(true);
    });

    it('provides source content, returnUrl, and isSavingChanges to the edit area', () => {
      expect(findEditArea().props()).toMatchObject({
        title,
        mounts,
        content,
        returnUrl,
        savingChanges: false,
      });
    });
  });

  it('does not render edit area when content is not loaded', () => {
    buildWrapper({ sourceContent: null });

    expect(findEditArea().exists()).toBe(false);
  });

  it('renders skeleton loader when content is not loading', () => {
    buildApollo({
      sourceContent: {
        loading: true,
      },
    });
    buildWrapper();

    expect(findSkeletonLoader().exists()).toBe(true);
  });

  it('does not render skeleton loader when content is not loading', () => {
    buildApollo({
      sourceContent: {
        loading: false,
      },
    });
    buildWrapper();

    expect(findSkeletonLoader().exists()).toBe(false);
  });

  it('displays invalid content message when content is not supported', () => {
    buildWrapper({ appData: { ...defaultAppData, isSupportedContent: false } });

    expect(findInvalidContentMessage().exists()).toBe(true);
  });

  it('does not display invalid content message when content is supported', () => {
    buildWrapper();

    expect(findInvalidContentMessage().exists()).toBe(false);
  });

  it('renders an EditMetaModal component', () => {
    buildWrapper();

    expect(findEditMetaModal().exists()).toBe(true);
  });

  describe('when preparing submission', () => {
    it('calls the show method when the edit-area submit event is emitted', () => {
      buildWrapper();

      const mockInstance = { show: jest.fn() };
      wrapper.vm.$refs.editMetaModal = mockInstance;

      findEditArea().vm.$emit('submit', { content });

      return wrapper.vm.$nextTick().then(() => {
        expect(mockInstance.show).toHaveBeenCalled();
      });
    });
  });

  describe('when submitting changes fails', () => {
    const setupMutateMock = () => {
      mutateMock
        .mockResolvedValueOnce(hasSubmittedChangesMutationPayload)
        .mockRejectedValueOnce(new Error(submitChangesError));
    };

    beforeEach(() => {
      setupMutateMock();

      buildWrapper({ content });
      findEditMetaModal().vm.$emit('primary', mergeRequestMeta);

      return wrapper.vm.$nextTick();
    });

    it('displays submit changes error message', () => {
      expect(findSubmitChangesError().exists()).toBe(true);
    });

    it('retries submitting changes when retry button is clicked', () => {
      setupMutateMock();

      findSubmitChangesError().vm.$emit('retry');

      expect(mutateMock).toHaveBeenCalled();
    });

    it('hides submit changes error message when dismiss button is clicked', () => {
      findSubmitChangesError().vm.$emit('dismiss');

      return wrapper.vm.$nextTick().then(() => {
        expect(findSubmitChangesError().exists()).toBe(false);
      });
    });
  });

  describe('when submitting changes succeeds', () => {
    const newContent = `new ${content}`;

    beforeEach(() => {
      mutateMock.mockResolvedValueOnce(hasSubmittedChangesMutationPayload).mockResolvedValueOnce({
        data: {
          submitContentChanges: savedContentMeta,
        },
      });

      buildWrapper({ content: newContent, images });
      findEditMetaModal().vm.$emit('primary', mergeRequestMeta);

      return wrapper.vm.$nextTick();
    });

    it('dispatches hasSubmittedChanges mutation', () => {
      expect(mutateMock).toHaveBeenNthCalledWith(1, {
        mutation: hasSubmittedChangesMutation,
        variables: {
          input: {
            hasSubmittedChanges: true,
          },
        },
      });
    });

    it('dispatches submitContentChanges mutation', () => {
      expect(mutateMock).toHaveBeenNthCalledWith(2, {
        mutation: submitContentChangesMutation,
        variables: {
          input: {
            content: newContent,
            project,
            sourcePath,
            username,
            images,
            mergeRequestMeta,
          },
        },
      });
    });

    it('transitions to the SUCCESS route', () => {
      expect($router.push).toHaveBeenCalledWith(SUCCESS_ROUTE);
    });
  });

  it('does not display submit changes error when an error does not exist', () => {
    buildWrapper();

    expect(findSubmitChangesError().exists()).toBe(false);
  });

  it('tracks when editor is initialized on the mounted lifecycle hook', () => {
    buildWrapper();
    expect(trackingSpy).toHaveBeenCalledWith(
      document.body.dataset.page,
      TRACKING_ACTION_INITIALIZE_EDITOR,
    );
  });
});
