import {
  GlFilteredSearchSuggestion,
  GlFilteredSearchTokenSegment,
  GlDropdownDivider,
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import {
  mockRegularLabel,
  mockLabels,
} from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';

import { DEFAULT_NONE_ANY } from '~/vue_shared/components/filtered_search_bar/constants';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';

import { mockLabelToken } from '../mock_data';

jest.mock('~/flash');
const defaultStubs = {
  Portal: true,
  BaseToken,
  GlFilteredSearchSuggestionList: {
    template: '<div></div>',
    methods: {
      getValue: () => '=',
    },
  },
};

function createComponent(options = {}) {
  const {
    config = mockLabelToken,
    value = { data: '' },
    active = false,
    stubs = defaultStubs,
    listeners = {},
  } = options;
  return mount(LabelToken, {
    propsData: {
      config,
      value,
      active,
    },
    provide: {
      portalName: 'fake target',
      alignSuggestions: function fakeAlignSuggestions() {},
      suggestionsListClass: () => 'custom-class',
    },
    stubs,
    listeners,
  });
}

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

  beforeEach(() => {
    mock = new MockAdapter(axios);
  });

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

  describe('methods', () => {
    beforeEach(() => {
      wrapper = createComponent();
    });

    describe('getActiveLabel', () => {
      it('returns label object from labels array based on provided `currentValue` param', () => {
        expect(wrapper.vm.getActiveLabel(mockLabels, 'foo label')).toEqual(mockRegularLabel);
      });
    });

    describe('getLabelName', () => {
      it('returns value of `name` or `title` property present in provided label param', () => {
        let mockLabel = {
          title: 'foo',
        };

        expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.title);

        mockLabel = {
          name: 'foo',
        };

        expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.name);
      });
    });

    describe('fetchLabels', () => {
      it('calls `config.fetchLabels` with provided searchTerm param', () => {
        jest.spyOn(wrapper.vm.config, 'fetchLabels');

        wrapper.vm.fetchLabels('foo');

        expect(wrapper.vm.config.fetchLabels).toHaveBeenCalledWith('foo');
      });

      it('sets response to `labels` when request is succesful', () => {
        jest.spyOn(wrapper.vm.config, 'fetchLabels').mockResolvedValue(mockLabels);

        wrapper.vm.fetchLabels('foo');

        return waitForPromises().then(() => {
          expect(wrapper.vm.labels).toEqual(mockLabels);
        });
      });

      it('calls `createFlash` with flash error message when request fails', () => {
        jest.spyOn(wrapper.vm.config, 'fetchLabels').mockRejectedValue({});

        wrapper.vm.fetchLabels('foo');

        return waitForPromises().then(() => {
          expect(createFlash).toHaveBeenCalledWith({
            message: 'There was a problem fetching labels.',
          });
        });
      });

      it('sets `loading` to false when request completes', () => {
        jest.spyOn(wrapper.vm.config, 'fetchLabels').mockRejectedValue({});

        wrapper.vm.fetchLabels('foo');

        return waitForPromises().then(() => {
          expect(wrapper.vm.loading).toBe(false);
        });
      });
    });
  });

  describe('template', () => {
    const defaultLabels = DEFAULT_NONE_ANY;

    beforeEach(async () => {
      wrapper = createComponent({ value: { data: `"${mockRegularLabel.title}"` } });

      wrapper.setData({
        labels: mockLabels,
      });

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

    it('renders base-token component', () => {
      const baseTokenEl = wrapper.find(BaseToken);

      expect(baseTokenEl.exists()).toBe(true);
      expect(baseTokenEl.props()).toMatchObject({
        suggestions: mockLabels,
        getActiveTokenValue: wrapper.vm.getActiveLabel,
      });
    });

    it('renders token item when value is selected', () => {
      const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);

      expect(tokenSegments).toHaveLength(3); // Label, =, "Foo Label"
      expect(tokenSegments.at(2).text()).toBe(`~${mockRegularLabel.title}`); // "Foo Label"
      expect(tokenSegments.at(2).find('.gl-token').attributes('style')).toBe(
        'background-color: rgb(186, 218, 85); color: rgb(255, 255, 255);',
      );
    });

    it('renders provided defaultLabels as suggestions', async () => {
      wrapper = createComponent({
        active: true,
        config: { ...mockLabelToken, defaultLabels },
        stubs: { Portal: true },
      });
      const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
      const suggestionsSegment = tokenSegments.at(2);
      suggestionsSegment.vm.$emit('activate');
      await wrapper.vm.$nextTick();

      const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);

      expect(suggestions).toHaveLength(defaultLabels.length);
      defaultLabels.forEach((label, index) => {
        expect(suggestions.at(index).text()).toBe(label.text);
      });
    });

    it('does not render divider when no defaultLabels', async () => {
      wrapper = createComponent({
        active: true,
        config: { ...mockLabelToken, defaultLabels: [] },
        stubs: { Portal: true },
      });
      const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
      const suggestionsSegment = tokenSegments.at(2);
      suggestionsSegment.vm.$emit('activate');
      await wrapper.vm.$nextTick();

      expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false);
      expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
    });

    it('renders `DEFAULT_NONE_ANY` as default suggestions', () => {
      wrapper = createComponent({
        active: true,
        config: { ...mockLabelToken },
        stubs: { Portal: true },
      });
      const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
      const suggestionsSegment = tokenSegments.at(2);
      suggestionsSegment.vm.$emit('activate');

      const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);

      expect(suggestions).toHaveLength(DEFAULT_NONE_ANY.length);
      DEFAULT_NONE_ANY.forEach((label, index) => {
        expect(suggestions.at(index).text()).toBe(label.text);
      });
    });

    it('emits listeners in the base-token', () => {
      const mockInput = jest.fn();
      wrapper = createComponent({
        listeners: {
          input: mockInput,
        },
      });
      wrapper.findComponent(BaseToken).vm.$emit('input', [{ data: 'mockData', operator: '=' }]);

      expect(mockInput).toHaveBeenLastCalledWith([{ data: 'mockData', operator: '=' }]);
    });
  });
});
