import { GlLabel } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import {
  labels as TEST_LABELS,
  mockIssue as TEST_ISSUE,
  mockIssueFullPath as TEST_ISSUE_FULLPATH,
} from 'jest/boards/mock_data';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import { createStore } from '~/boards/stores';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';

const TEST_LABELS_PAYLOAD = TEST_LABELS.map((label) => ({ ...label, set: true }));
const TEST_LABELS_TITLES = TEST_LABELS.map((label) => label.title);

describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
  let wrapper;
  let store;

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

  const createWrapper = ({ labels = [], providedValues = {} } = {}) => {
    store = createStore();
    store.state.boardItems = { [TEST_ISSUE.id]: { ...TEST_ISSUE, labels } };
    store.state.activeId = TEST_ISSUE.id;

    wrapper = shallowMount(BoardSidebarLabelsSelect, {
      store,
      provide: {
        canUpdate: true,
        labelsManagePath: TEST_HOST,
        labelsFilterBasePath: TEST_HOST,
        ...providedValues,
      },
      stubs: {
        BoardEditableItem,
        LabelsSelect: true,
      },
    });
  };

  const findLabelsSelect = () => wrapper.find({ ref: 'labelsSelect' });
  const findLabelsTitles = () =>
    wrapper.findAll(GlLabel).wrappers.map((item) => item.props('title'));
  const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');

  describe('when labelsFetchPath is provided', () => {
    it('uses injected labels fetch path', () => {
      createWrapper({ providedValues: { labelsFetchPath: 'foobar' } });

      expect(findLabelsSelect().props('labelsFetchPath')).toEqual('foobar');
    });
  });

  it('uses the default project label endpoint', () => {
    createWrapper();

    expect(findLabelsSelect().props('labelsFetchPath')).toEqual(
      `/${TEST_ISSUE_FULLPATH}/-/labels?include_ancestor_groups=true`,
    );
  });

  it('renders "None" when no labels are selected', () => {
    createWrapper();

    expect(findCollapsed().text()).toBe('None');
  });

  it('renders labels when set', () => {
    createWrapper({ labels: TEST_LABELS });

    expect(findLabelsTitles()).toEqual(TEST_LABELS_TITLES);
  });

  describe('when labels are submitted', () => {
    beforeEach(async () => {
      createWrapper();

      jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => TEST_LABELS);
      findLabelsSelect().vm.$emit('updateSelectedLabels', TEST_LABELS_PAYLOAD);
      store.state.boardItems[TEST_ISSUE.id].labels = TEST_LABELS;
      await wrapper.vm.$nextTick();
    });

    it('collapses sidebar and renders labels', () => {
      expect(findCollapsed().isVisible()).toBe(true);
      expect(findLabelsTitles()).toEqual(TEST_LABELS_TITLES);
    });

    it('commits change to the server', () => {
      expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
        addLabelIds: TEST_LABELS.map((label) => label.id),
        projectPath: TEST_ISSUE_FULLPATH,
        removeLabelIds: [],
        iid: null,
      });
    });
  });

  describe('when labels are updated over existing labels', () => {
    const testLabelsPayload = [
      { id: 5, set: true },
      { id: 6, set: false },
      { id: 7, set: true },
    ];
    const expectedLabels = [{ id: 5 }, { id: 7 }];

    beforeEach(async () => {
      createWrapper({ labels: TEST_LABELS });

      jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => expectedLabels);
      findLabelsSelect().vm.$emit('updateSelectedLabels', testLabelsPayload);
      await wrapper.vm.$nextTick();
    });

    it('commits change to the server', () => {
      expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
        addLabelIds: [5, 7],
        removeLabelIds: [6],
        projectPath: TEST_ISSUE_FULLPATH,
        iid: null,
      });
    });
  });

  describe('when removing individual labels', () => {
    const testLabel = TEST_LABELS[0];

    beforeEach(async () => {
      createWrapper({ labels: [testLabel] });

      jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => {});
    });

    it('commits change to the server', () => {
      wrapper.find(GlLabel).vm.$emit('close', testLabel);

      expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
        removeLabelIds: [getIdFromGraphQLId(testLabel.id)],
        projectPath: TEST_ISSUE_FULLPATH,
      });
    });
  });

  describe('when the mutation fails', () => {
    beforeEach(async () => {
      createWrapper({ labels: TEST_LABELS });

      jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => {
        throw new Error(['failed mutation']);
      });
      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
      findLabelsSelect().vm.$emit('updateSelectedLabels', [{ id: '?' }]);
      await wrapper.vm.$nextTick();
    });

    it('collapses sidebar and renders former issue weight', () => {
      expect(findCollapsed().isVisible()).toBe(true);
      expect(findLabelsTitles()).toEqual(TEST_LABELS_TITLES);
      expect(wrapper.vm.setError).toHaveBeenCalled();
    });
  });
});
