import {
  GlButton,
  GlDropdown,
  GlDropdownItem,
  GlDropdownSectionHeader,
  GlSearchBoxByType,
} from '@gitlab/ui';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import eventHub from '~/projects/new/event_hub';
import NewProjectUrlSelect from '~/projects/new/components/new_project_url_select.vue';
import searchQuery from '~/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql';

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

  const data = {
    currentUser: {
      groups: {
        nodes: [
          {
            id: 'gid://gitlab/Group/26',
            fullPath: 'flightjs',
            name: 'Flight JS',
            visibility: 'public',
            webUrl: 'http://127.0.0.1:3000/flightjs',
          },
          {
            id: 'gid://gitlab/Group/28',
            fullPath: 'h5bp',
            name: 'H5BP',
            visibility: 'public',
            webUrl: 'http://127.0.0.1:3000/h5bp',
          },
          {
            id: 'gid://gitlab/Group/30',
            fullPath: 'h5bp/subgroup',
            name: 'H5BP Subgroup',
            visibility: 'private',
            webUrl: 'http://127.0.0.1:3000/h5bp/subgroup',
          },
        ],
      },
      namespace: {
        id: 'gid://gitlab/Namespace/1',
        fullPath: 'root',
      },
    },
  };

  const localVue = createLocalVue();
  localVue.use(VueApollo);

  const defaultProvide = {
    namespaceFullPath: 'h5bp',
    namespaceId: '28',
    rootUrl: 'https://gitlab.com/',
    trackLabel: 'blank_project',
    userNamespaceFullPath: 'root',
    userNamespaceId: '1',
  };

  const mountComponent = ({
    search = '',
    queryResponse = data,
    provide = defaultProvide,
    mountFn = shallowMount,
  } = {}) => {
    const requestHandlers = [[searchQuery, jest.fn().mockResolvedValue({ data: queryResponse })]];
    const apolloProvider = createMockApollo(requestHandlers);

    return mountFn(NewProjectUrlSelect, {
      localVue,
      apolloProvider,
      provide,
      data() {
        return {
          search,
        };
      },
    });
  };

  const findButtonLabel = () => wrapper.findComponent(GlButton);
  const findDropdown = () => wrapper.findComponent(GlDropdown);
  const findInput = () => wrapper.findComponent(GlSearchBoxByType);
  const findHiddenInput = () => wrapper.find('input');
  const clickDropdownItem = async () => {
    wrapper.findComponent(GlDropdownItem).vm.$emit('click');
    await wrapper.vm.$nextTick();
  };

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

  it('renders the root url as a label', () => {
    wrapper = mountComponent();

    expect(findButtonLabel().text()).toBe(defaultProvide.rootUrl);
    expect(findButtonLabel().props('label')).toBe(true);
  });

  describe('when namespaceId is provided', () => {
    beforeEach(() => {
      wrapper = mountComponent();
    });

    it('renders a dropdown with the given namespace full path as the text', () => {
      expect(findDropdown().props('text')).toBe(defaultProvide.namespaceFullPath);
    });

    it('renders a dropdown with the given namespace id in the hidden input', () => {
      expect(findHiddenInput().attributes('value')).toBe(defaultProvide.namespaceId);
    });
  });

  describe('when namespaceId is not provided', () => {
    const provide = {
      ...defaultProvide,
      namespaceFullPath: undefined,
      namespaceId: undefined,
    };

    beforeEach(() => {
      wrapper = mountComponent({ provide });
    });

    it("renders a dropdown with the user's namespace full path as the text", () => {
      expect(findDropdown().props('text')).toBe(defaultProvide.userNamespaceFullPath);
    });

    it("renders a dropdown with the user's namespace id in the hidden input", () => {
      expect(findHiddenInput().attributes('value')).toBe(defaultProvide.userNamespaceId);
    });
  });

  it('focuses on the input when the dropdown is opened', async () => {
    wrapper = mountComponent({ mountFn: mount });
    jest.runOnlyPendingTimers();
    await wrapper.vm.$nextTick();

    const spy = jest.spyOn(findInput().vm, 'focusInput');

    findDropdown().vm.$emit('shown');

    expect(spy).toHaveBeenCalledTimes(1);
  });

  it('renders expected dropdown items', async () => {
    wrapper = mountComponent({ mountFn: mount });
    jest.runOnlyPendingTimers();
    await wrapper.vm.$nextTick();

    const listItems = wrapper.findAll('li');

    expect(listItems).toHaveLength(6);
    expect(listItems.at(0).findComponent(GlDropdownSectionHeader).text()).toBe('Groups');
    expect(listItems.at(1).text()).toBe(data.currentUser.groups.nodes[0].fullPath);
    expect(listItems.at(2).text()).toBe(data.currentUser.groups.nodes[1].fullPath);
    expect(listItems.at(3).text()).toBe(data.currentUser.groups.nodes[2].fullPath);
    expect(listItems.at(4).findComponent(GlDropdownSectionHeader).text()).toBe('Users');
    expect(listItems.at(5).text()).toBe(data.currentUser.namespace.fullPath);
  });

  describe('when selecting from a group template', () => {
    const groupId = getIdFromGraphQLId(data.currentUser.groups.nodes[1].id);

    beforeEach(async () => {
      wrapper = mountComponent({ mountFn: mount });
      jest.runOnlyPendingTimers();
      await wrapper.vm.$nextTick();

      eventHub.$emit('select-template', groupId);
    });

    it('filters the dropdown items to the selected group and children', async () => {
      const listItems = wrapper.findAll('li');

      expect(listItems).toHaveLength(3);
      expect(listItems.at(0).findComponent(GlDropdownSectionHeader).text()).toBe('Groups');
      expect(listItems.at(1).text()).toBe(data.currentUser.groups.nodes[1].fullPath);
      expect(listItems.at(2).text()).toBe(data.currentUser.groups.nodes[2].fullPath);
    });

    it('sets the selection to the group', async () => {
      expect(findDropdown().props('text')).toBe(data.currentUser.groups.nodes[1].fullPath);
    });
  });

  it('renders `No matches found` when there are no matching dropdown items', async () => {
    const queryResponse = {
      currentUser: {
        groups: {
          nodes: [],
        },
        namespace: {
          id: 'gid://gitlab/Namespace/1',
          fullPath: 'root',
        },
      },
    };

    wrapper = mountComponent({ search: 'no matches', queryResponse, mountFn: mount });
    jest.runOnlyPendingTimers();
    await wrapper.vm.$nextTick();

    expect(wrapper.find('li').text()).toBe('No matches found');
  });

  it('emits `update-visibility` event to update the visibility radio options', async () => {
    wrapper = mountComponent();
    jest.runOnlyPendingTimers();
    await wrapper.vm.$nextTick();

    const spy = jest.spyOn(eventHub, '$emit');

    await clickDropdownItem();

    const namespace = data.currentUser.groups.nodes[0];

    expect(spy).toHaveBeenCalledWith('update-visibility', {
      name: namespace.name,
      visibility: namespace.visibility,
      showPath: namespace.webUrl,
      editPath: `${namespace.webUrl}/-/edit`,
    });
  });

  it('updates hidden input with selected namespace', async () => {
    wrapper = mountComponent();
    jest.runOnlyPendingTimers();
    await wrapper.vm.$nextTick();

    await clickDropdownItem();

    expect(findHiddenInput().attributes()).toMatchObject({
      name: 'project[namespace_id]',
      value: getIdFromGraphQLId(data.currentUser.groups.nodes[0].id).toString(),
    });
  });

  it('tracks clicking on the dropdown', () => {
    wrapper = mountComponent();

    const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);

    findDropdown().vm.$emit('show');

    expect(trackingSpy).toHaveBeenCalledWith(undefined, 'activate_form_input', {
      label: defaultProvide.trackLabel,
      property: 'project_path',
    });

    unmockTracking();
  });
});
