import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue';
import getProjects from '~/analytics/shared/graphql/projects.query.graphql';

const projects = [
  {
    id: 'gid://gitlab/Project/1',
    name: 'Gitlab Test',
    fullPath: 'gitlab-org/gitlab-test',
    avatarUrl: `${TEST_HOST}/images/home/nasa.svg`,
  },
  {
    id: 'gid://gitlab/Project/2',
    name: 'Gitlab Shell',
    fullPath: 'gitlab-org/gitlab-shell',
    avatarUrl: null,
  },
  {
    id: 'gid://gitlab/Project/3',
    name: 'Foo',
    fullPath: 'gitlab-org/foo',
    avatarUrl: null,
  },
];

const MockGlDropdown = stubComponent(GlDropdown, {
  template: `
  <div>
    <div data-testid="vsa-highlighted-items">
      <slot name="highlighted-items"></slot>
    </div>
    <div data-testid="vsa-default-items"><slot></slot></div>
  </div>
  `,
});

const defaultMocks = {
  $apollo: {
    query: jest.fn().mockResolvedValue({
      data: { group: { projects: { nodes: projects } } },
    }),
  },
};

let spyQuery;

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

  const createComponent = (props = {}, stubs = {}) => {
    spyQuery = defaultMocks.$apollo.query;
    wrapper = mountExtended(ProjectsDropdownFilter, {
      mocks: { ...defaultMocks },
      propsData: {
        groupId: 1,
        groupNamespace: 'gitlab-org',
        ...props,
      },
      stubs,
    });
  };

  const createWithMockDropdown = (props) => {
    createComponent(props, { GlDropdown: MockGlDropdown });
    return waitForPromises();
  };

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

  const findHighlightedItems = () => wrapper.findByTestId('vsa-highlighted-items');
  const findUnhighlightedItems = () => wrapper.findByTestId('vsa-default-items');
  const findHighlightedItemsTitle = () => wrapper.findByText('Selected');
  const findClearAllButton = () => wrapper.findByText('Clear all');

  const findDropdown = () => wrapper.find(GlDropdown);

  const findDropdownItems = () =>
    findDropdown()
      .findAll(GlDropdownItem)
      .filter((w) => w.text() !== 'No matching results');

  const findDropdownAtIndex = (index) => findDropdownItems().at(index);

  const findDropdownButton = () => findDropdown().find('.dropdown-toggle');
  const findDropdownButtonAvatar = () => findDropdown().find('.gl-avatar');
  const findDropdownButtonAvatarAtIndex = (index) =>
    findDropdownAtIndex(index).find('img.gl-avatar');
  const findDropdownButtonIdentIconAtIndex = (index) =>
    findDropdownAtIndex(index).find('div.gl-avatar-identicon');

  const findDropdownNameAtIndex = (index) =>
    findDropdownAtIndex(index).find('[data-testid="project-name"');
  const findDropdownFullPathAtIndex = (index) =>
    findDropdownAtIndex(index).find('[data-testid="project-full-path"]');

  const selectDropdownItemAtIndex = (index) => {
    findDropdownAtIndex(index).find('button').trigger('click');
    return wrapper.vm.$nextTick();
  };

  // NOTE: Selected items are now visually separated from unselected items
  const findSelectedDropdownItems = () => findHighlightedItems().findAll(GlDropdownItem);

  const findSelectedDropdownAtIndex = (index) => findSelectedDropdownItems().at(index);
  const findSelectedButtonIdentIconAtIndex = (index) =>
    findSelectedDropdownAtIndex(index).find('div.gl-avatar-identicon');
  const findSelectedButtonAvatarItemAtIndex = (index) =>
    findSelectedDropdownAtIndex(index).find('img.gl-avatar');

  const selectedIds = () => wrapper.vm.selectedProjects.map(({ id }) => id);

  describe('queryParams are applied when fetching data', () => {
    beforeEach(() => {
      createComponent({
        queryParams: {
          first: 50,
          includeSubgroups: true,
        },
      });
    });

    it('applies the correct queryParams when making an api call', async () => {
      wrapper.setData({ searchTerm: 'gitlab' });

      expect(spyQuery).toHaveBeenCalledTimes(1);

      await wrapper.vm.$nextTick(() => {
        expect(spyQuery).toHaveBeenCalledWith({
          query: getProjects,
          variables: {
            search: 'gitlab',
            groupFullPath: wrapper.vm.groupNamespace,
            first: 50,
            includeSubgroups: true,
          },
        });
      });
    });
  });

  describe('highlighted items', () => {
    const blockDefaultProps = { multiSelect: true };
    beforeEach(() => {
      createComponent(blockDefaultProps);
    });

    describe('with no project selected', () => {
      it('does not render the highlighted items', async () => {
        await createWithMockDropdown(blockDefaultProps);
        expect(findSelectedDropdownItems().length).toBe(0);
      });

      it('does not render the highlighted items title', () => {
        expect(findHighlightedItemsTitle().exists()).toBe(false);
      });

      it('does not render the clear all button', () => {
        expect(findClearAllButton().exists()).toBe(false);
      });
    });

    describe('with a selected project', () => {
      beforeEach(async () => {
        await selectDropdownItemAtIndex(0);
      });

      it('renders the highlighted items', async () => {
        await createWithMockDropdown(blockDefaultProps);
        await selectDropdownItemAtIndex(0);

        expect(findSelectedDropdownItems().length).toBe(1);
      });

      it('renders the highlighted items title', () => {
        expect(findHighlightedItemsTitle().exists()).toBe(true);
      });

      it('renders the clear all button', () => {
        expect(findClearAllButton().exists()).toBe(true);
      });

      it('clears all selected items when the clear all button is clicked', async () => {
        await selectDropdownItemAtIndex(1);

        expect(wrapper.text()).toContain('2 projects selected');

        findClearAllButton().trigger('click');
        await wrapper.vm.$nextTick();

        expect(wrapper.text()).not.toContain('2 projects selected');
        expect(wrapper.text()).toContain('Select projects');
      });
    });
  });

  describe('with a selected project and search term', () => {
    beforeEach(async () => {
      await createWithMockDropdown({ multiSelect: true });

      selectDropdownItemAtIndex(0);
      wrapper.setData({ searchTerm: 'this is a very long search string' });
    });

    it('renders the highlighted items', async () => {
      expect(findUnhighlightedItems().findAll('li').length).toBe(1);
    });

    it('hides the unhighlighted items that do not match the string', async () => {
      expect(findUnhighlightedItems().findAll('li').length).toBe(1);
      expect(findUnhighlightedItems().text()).toContain('No matching results');
    });
  });

  describe('when passed an array of defaultProject as prop', () => {
    beforeEach(() => {
      createComponent({
        defaultProjects: [projects[0]],
      });
    });

    it("displays the defaultProject's name", () => {
      expect(findDropdownButton().text()).toContain(projects[0].name);
    });

    it("renders the defaultProject's avatar", () => {
      expect(findDropdownButtonAvatar().exists()).toBe(true);
    });

    it('marks the defaultProject as selected', () => {
      expect(findDropdownAtIndex(0).props('isChecked')).toBe(true);
    });
  });

  describe('when multiSelect is false', () => {
    const blockDefaultProps = { multiSelect: false };
    beforeEach(() => {
      createComponent(blockDefaultProps);
    });

    describe('displays the correct information', () => {
      it('contains 3 items', () => {
        expect(findDropdownItems()).toHaveLength(3);
      });

      it('renders an avatar when the project has an avatarUrl', () => {
        expect(findDropdownButtonAvatarAtIndex(0).exists()).toBe(true);
        expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
      });

      it("renders an identicon when the project doesn't have an avatarUrl", () => {
        expect(findDropdownButtonAvatarAtIndex(1).exists()).toBe(false);
        expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
      });

      it('renders the project name', () => {
        projects.forEach((project, index) => {
          expect(findDropdownNameAtIndex(index).text()).toBe(project.name);
        });
      });

      it('renders the project fullPath', () => {
        projects.forEach((project, index) => {
          expect(findDropdownFullPathAtIndex(index).text()).toBe(project.fullPath);
        });
      });
    });

    describe('on project click', () => {
      it('should emit the "selected" event with the selected project', () => {
        selectDropdownItemAtIndex(0);

        expect(wrapper.emitted().selected).toEqual([[[projects[0]]]]);
      });

      it('should change selection when new project is clicked', () => {
        selectDropdownItemAtIndex(1);

        expect(wrapper.emitted().selected).toEqual([[[projects[1]]]]);
      });

      it('selection should be emptied when a project is deselected', () => {
        selectDropdownItemAtIndex(0); // Select the item
        selectDropdownItemAtIndex(0); // deselect it

        expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[]]]);
      });

      it('renders an avatar in the dropdown button when the project has an avatarUrl', async () => {
        await createWithMockDropdown(blockDefaultProps);
        await selectDropdownItemAtIndex(0);

        expect(findSelectedButtonAvatarItemAtIndex(0).exists()).toBe(true);
        expect(findSelectedButtonIdentIconAtIndex(0).exists()).toBe(false);
      });

      it("renders an identicon in the dropdown button when the project doesn't have an avatarUrl", async () => {
        await createWithMockDropdown(blockDefaultProps);
        await selectDropdownItemAtIndex(1);

        expect(findSelectedButtonAvatarItemAtIndex(0).exists()).toBe(false);
        expect(findSelectedButtonIdentIconAtIndex(0).exists()).toBe(true);
      });
    });
  });

  describe('when multiSelect is true', () => {
    beforeEach(() => {
      createComponent({ multiSelect: true });
    });

    describe('displays the correct information', () => {
      it('contains 3 items', () => {
        expect(findDropdownItems()).toHaveLength(3);
      });

      it('renders an avatar when the project has an avatarUrl', () => {
        expect(findDropdownButtonAvatarAtIndex(0).exists()).toBe(true);
        expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
      });

      it("renders an identicon when the project doesn't have an avatarUrl", () => {
        expect(findDropdownButtonAvatarAtIndex(1).exists()).toBe(false);
        expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
      });

      it('renders the project name', () => {
        projects.forEach((project, index) => {
          expect(findDropdownNameAtIndex(index).text()).toBe(project.name);
        });
      });

      it('renders the project fullPath', () => {
        projects.forEach((project, index) => {
          expect(findDropdownFullPathAtIndex(index).text()).toBe(project.fullPath);
        });
      });
    });

    describe('on project click', () => {
      it('should add to selection when new project is clicked', () => {
        selectDropdownItemAtIndex(0);
        selectDropdownItemAtIndex(1);

        expect(selectedIds()).toEqual([projects[0].id, projects[1].id]);
      });

      it('should remove from selection when clicked again', () => {
        selectDropdownItemAtIndex(0);
        expect(selectedIds()).toEqual([projects[0].id]);

        selectDropdownItemAtIndex(0);
        expect(selectedIds()).toEqual([]);
      });

      it('renders the correct placeholder text when multiple projects are selected', async () => {
        selectDropdownItemAtIndex(0);
        selectDropdownItemAtIndex(1);

        await wrapper.vm.$nextTick().then(() => {
          expect(findDropdownButton().text()).toBe('2 projects selected');
        });
      });
    });
  });
});
