import { mount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import notes from '../../mock_data/notes';
import DesignDiscussion from '~/design_management/components/design_notes/design_discussion.vue';
import DesignNote from '~/design_management/components/design_notes/design_note.vue';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
import createNoteMutation from '~/design_management/graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '~/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import ToggleRepliesWidget from '~/design_management/components/design_notes/toggle_replies_widget.vue';
import mockDiscussion from '../../mock_data/discussion';

const defaultMockDiscussion = {
  id: '0',
  resolved: false,
  resolvable: true,
  notes,
};

describe('Design discussions component', () => {
  let wrapper;

  const findDesignNotes = () => wrapper.findAll(DesignNote);
  const findReplyPlaceholder = () => wrapper.find(ReplyPlaceholder);
  const findReplyForm = () => wrapper.find(DesignReplyForm);
  const findRepliesWidget = () => wrapper.find(ToggleRepliesWidget);
  const findResolveButton = () => wrapper.find('[data-testid="resolve-button"]');
  const findResolveIcon = () => wrapper.find('[data-testid="resolve-icon"]');
  const findResolvedMessage = () => wrapper.find('[data-testid="resolved-message"]');
  const findResolveLoadingIcon = () => wrapper.find(GlLoadingIcon);
  const findResolveCheckbox = () => wrapper.find('[data-testid="resolve-checkbox"]');

  const mutationVariables = {
    mutation: createNoteMutation,
    variables: {
      input: {
        noteableId: 'noteable-id',
        body: 'test',
        discussionId: '0',
      },
    },
  };
  const mutate = jest.fn().mockResolvedValue({ data: { createNote: { errors: [] } } });
  const $apollo = {
    mutate,
  };

  function createComponent(props = {}, data = {}) {
    wrapper = mount(DesignDiscussion, {
      propsData: {
        resolvedDiscussionsExpanded: true,
        discussion: defaultMockDiscussion,
        noteableId: 'noteable-id',
        designId: 'design-id',
        discussionIndex: 1,
        discussionWithOpenForm: '',
        ...props,
      },
      data() {
        return {
          ...data,
        };
      },
      provide: {
        projectPath: 'project-path',
        issueIid: '1',
      },
      mocks: {
        $apollo,
        $route: {
          hash: '#note_1',
        },
      },
    });
  }

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

  describe('when discussion is not resolvable', () => {
    beforeEach(() => {
      createComponent({
        discussion: {
          ...defaultMockDiscussion,
          resolvable: false,
        },
      });
    });

    it('does not render an icon to resolve a thread', () => {
      expect(findResolveIcon().exists()).toBe(false);
    });

    it('does not render a checkbox in reply form', () => {
      findReplyPlaceholder().vm.$emit('onClick');

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

  describe('when discussion is unresolved', () => {
    beforeEach(() => {
      createComponent();
    });

    it('renders correct amount of discussion notes', () => {
      expect(findDesignNotes()).toHaveLength(2);
      expect(findDesignNotes().wrappers.every(w => w.isVisible())).toBe(true);
    });

    it('renders reply placeholder', () => {
      expect(findReplyPlaceholder().isVisible()).toBe(true);
    });

    it('does not render toggle replies widget', () => {
      expect(findRepliesWidget().exists()).toBe(false);
    });

    it('renders a correct icon to resolve a thread', () => {
      expect(findResolveIcon().props('name')).toBe('check-circle');
    });

    it('renders a checkbox with Resolve thread text in reply form', () => {
      findReplyPlaceholder().vm.$emit('onClick');
      wrapper.setProps({ discussionWithOpenForm: defaultMockDiscussion.id });

      return wrapper.vm.$nextTick().then(() => {
        expect(findResolveCheckbox().text()).toBe('Resolve thread');
      });
    });

    it('does not render resolved message', () => {
      expect(findResolvedMessage().exists()).toBe(false);
    });
  });

  describe('when discussion is resolved', () => {
    beforeEach(() => {
      createComponent({
        discussion: {
          ...defaultMockDiscussion,
          resolved: true,
          resolvedBy: notes[0].author,
          resolvedAt: '2020-05-08T07:10:45Z',
        },
      });
    });

    it('shows only the first note', () => {
      expect(
        findDesignNotes()
          .at(0)
          .isVisible(),
      ).toBe(true);
      expect(
        findDesignNotes()
          .at(1)
          .isVisible(),
      ).toBe(false);
    });

    it('renders resolved message', () => {
      expect(findResolvedMessage().exists()).toBe(true);
    });

    it('does not show renders reply placeholder', () => {
      expect(findReplyPlaceholder().isVisible()).toBe(false);
    });

    it('renders toggle replies widget with correct props', () => {
      expect(findRepliesWidget().exists()).toBe(true);
      expect(findRepliesWidget().props()).toEqual({
        collapsed: true,
        replies: notes.slice(1),
      });
    });

    it('renders a correct icon to resolve a thread', () => {
      expect(findResolveIcon().props('name')).toBe('check-circle-filled');
    });

    describe('when replies are expanded', () => {
      beforeEach(() => {
        findRepliesWidget().vm.$emit('toggle');
        return wrapper.vm.$nextTick();
      });

      it('renders replies widget with collapsed prop equal to false', () => {
        expect(findRepliesWidget().props('collapsed')).toBe(false);
      });

      it('renders the second note', () => {
        expect(
          findDesignNotes()
            .at(1)
            .isVisible(),
        ).toBe(true);
      });

      it('renders a reply placeholder', () => {
        expect(findReplyPlaceholder().isVisible()).toBe(true);
      });

      it('renders a checkbox with Unresolve thread text in reply form', () => {
        findReplyPlaceholder().vm.$emit('onClick');
        wrapper.setProps({ discussionWithOpenForm: defaultMockDiscussion.id });

        return wrapper.vm.$nextTick().then(() => {
          expect(findResolveCheckbox().text()).toBe('Unresolve thread');
        });
      });
    });
  });

  it('hides reply placeholder and opens form on placeholder click', () => {
    createComponent();
    findReplyPlaceholder().vm.$emit('onClick');
    wrapper.setProps({ discussionWithOpenForm: defaultMockDiscussion.id });

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

  it('calls mutation on submitting form and closes the form', async () => {
    createComponent(
      { discussionWithOpenForm: defaultMockDiscussion.id },
      { discussionComment: 'test', isFormRendered: true },
    );

    findReplyForm().vm.$emit('submit-form');
    expect(mutate).toHaveBeenCalledWith(mutationVariables);

    await mutate();
    await wrapper.vm.$nextTick();

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

  it('clears the discussion comment on closing comment form', () => {
    createComponent(
      { discussionWithOpenForm: defaultMockDiscussion.id },
      { discussionComment: 'test', isFormRendered: true },
    );

    return wrapper.vm
      .$nextTick()
      .then(() => {
        findReplyForm().vm.$emit('cancel-form');

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

  describe('when any note from a discussion is active', () => {
    it.each([notes[0], notes[0].discussion.notes.nodes[1]])(
      'applies correct class to all notes in the active discussion',
      note => {
        createComponent(
          { discussion: mockDiscussion },
          {
            activeDiscussion: {
              id: note.id,
              source: 'pin',
            },
          },
        );

        expect(
          wrapper
            .findAll(DesignNote)
            .wrappers.every(designNote => designNote.classes('gl-bg-blue-50')),
        ).toBe(true);
      },
    );
  });

  it('calls toggleResolveDiscussion mutation on resolve thread button click', () => {
    createComponent();
    findResolveButton().trigger('click');
    expect(mutate).toHaveBeenCalledWith({
      mutation: toggleResolveDiscussionMutation,
      variables: {
        id: defaultMockDiscussion.id,
        resolve: true,
      },
    });
    return wrapper.vm.$nextTick(() => {
      expect(findResolveLoadingIcon().exists()).toBe(true);
    });
  });

  it('calls toggleResolveDiscussion mutation after adding a note if checkbox was checked', () => {
    createComponent(
      { discussionWithOpenForm: defaultMockDiscussion.id },
      { discussionComment: 'test', isFormRendered: true },
    );
    findResolveButton().trigger('click');
    findReplyForm().vm.$emit('submitForm');

    return mutate().then(() => {
      expect(mutate).toHaveBeenCalledWith({
        mutation: toggleResolveDiscussionMutation,
        variables: {
          id: defaultMockDiscussion.id,
          resolve: true,
        },
      });
    });
  });

  it('emits openForm event on opening the form', () => {
    createComponent();
    findReplyPlaceholder().vm.$emit('onClick');

    expect(wrapper.emitted('open-form')).toBeTruthy();
  });
});
