import { mount, createLocalVue } from '@vue/test-utils';
import {
  GlDropdownItem,
  GlAvatarLink,
  GlAvatarLabeled,
  GlSearchBoxByType,
  GlLoadingIcon,
} from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import VueApollo from 'vue-apollo';
import BoardAssigneeDropdown from '~/boards/components/board_assignee_dropdown.vue';
import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees.vue';
import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import store from '~/boards/stores';
import getIssueParticipants from '~/vue_shared/components/sidebar/queries/getIssueParticipants.query.graphql';
import searchUsers from '~/boards/graphql/users_search.query.graphql';
import { participants } from '../mock_data';

const localVue = createLocalVue();

localVue.use(VueApollo);

describe('BoardCardAssigneeDropdown', () => {
  let wrapper;
  let fakeApollo;
  let getIssueParticipantsSpy;
  let getSearchUsersSpy;
  let dispatchSpy;

  const iid = '111';
  const activeIssueName = 'test';
  const anotherIssueName = 'hello';

  const createComponent = (search = '', loading = false) => {
    wrapper = mount(BoardAssigneeDropdown, {
      data() {
        return {
          search,
          selected: [],
          participants,
        };
      },
      store,
      provide: {
        canUpdate: true,
        rootPath: '',
      },
      mocks: {
        $apollo: {
          queries: {
            participants: {
              loading,
            },
          },
        },
      },
    });
  };

  const createComponentWithApollo = (search = '') => {
    fakeApollo = createMockApollo([
      [getIssueParticipants, getIssueParticipantsSpy],
      [searchUsers, getSearchUsersSpy],
    ]);
    wrapper = mount(BoardAssigneeDropdown, {
      localVue,
      apolloProvider: fakeApollo,
      data() {
        return {
          search,
          selected: [],
          participants,
        };
      },
      store,
      provide: {
        canUpdate: true,
        rootPath: '',
      },
    });
  };

  const unassign = async () => {
    wrapper.find('[data-testid="unassign"]').trigger('click');

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

  const openDropdown = async () => {
    wrapper.find('[data-testid="edit-button"]').trigger('click');

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

  const findByText = (text) => {
    return wrapper.findAll(GlDropdownItem).wrappers.find((node) => node.text().indexOf(text) === 0);
  };

  const findLoadingIcon = () => wrapper.find(GlLoadingIcon);

  beforeEach(() => {
    store.state.activeId = '1';
    store.state.issues = {
      1: {
        iid,
        assignees: [{ username: activeIssueName, name: activeIssueName, id: activeIssueName }],
      },
    };

    dispatchSpy = jest.spyOn(store, 'dispatch').mockResolvedValue();
  });

  afterEach(() => {
    window.gon = {};
    jest.restoreAllMocks();
  });

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

  describe('when mounted', () => {
    beforeEach(() => {
      createComponent();
    });

    it.each`
      text
      ${anotherIssueName}
      ${activeIssueName}
    `('finds item with $text', ({ text }) => {
      const item = findByText(text);

      expect(item.exists()).toBe(true);
    });

    it('renders gl-avatar-link in gl-dropdown-item', () => {
      const item = findByText('hello');

      expect(item.find(GlAvatarLink).exists()).toBe(true);
    });

    it('renders gl-avatar-labeled in gl-avatar-link', () => {
      const item = findByText('hello');

      expect(item.find(GlAvatarLink).find(GlAvatarLabeled).exists()).toBe(true);
    });
  });

  describe('when selected users are present', () => {
    it('renders a divider', () => {
      createComponent();

      expect(wrapper.find('[data-testid="selected-user-divider"]').exists()).toBe(true);
    });
  });

  describe('when collapsed', () => {
    it('renders IssuableAssignees', () => {
      createComponent();

      expect(wrapper.find(IssuableAssignees).isVisible()).toBe(true);
      expect(wrapper.find(MultiSelectDropdown).isVisible()).toBe(false);
    });
  });

  describe('when dropdown is open', () => {
    beforeEach(async () => {
      createComponent();

      await openDropdown();
    });

    it('shows assignees dropdown', async () => {
      expect(wrapper.find(IssuableAssignees).isVisible()).toBe(false);
      expect(wrapper.find(MultiSelectDropdown).isVisible()).toBe(true);
    });

    it('shows the issue returned as the activeIssue', async () => {
      expect(findByText(activeIssueName).props('isChecked')).toBe(true);
    });

    describe('when "Unassign" is clicked', () => {
      it('unassigns assignees', async () => {
        await unassign();

        expect(findByText('Unassign').props('isChecked')).toBe(true);
      });
    });

    describe('when an unselected item is clicked', () => {
      beforeEach(async () => {
        await unassign();
      });

      it('assigns assignee in the dropdown', async () => {
        wrapper.find('[data-testid="item_test"]').trigger('click');

        await wrapper.vm.$nextTick();

        expect(findByText(activeIssueName).props('isChecked')).toBe(true);
      });

      it('calls setAssignees with username list', async () => {
        wrapper.find('[data-testid="item_test"]').trigger('click');

        await wrapper.vm.$nextTick();

        document.body.click();

        await wrapper.vm.$nextTick();

        expect(store.dispatch).toHaveBeenCalledWith('setAssignees', [activeIssueName]);
      });
    });

    describe('when the user off clicks', () => {
      beforeEach(async () => {
        await unassign();

        document.body.click();

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

      it('calls setAssignees with username list', async () => {
        expect(store.dispatch).toHaveBeenCalledWith('setAssignees', []);
      });

      it('closes the dropdown', async () => {
        expect(wrapper.find(IssuableAssignees).isVisible()).toBe(true);
      });
    });
  });

  it('renders divider after unassign', () => {
    createComponent();

    expect(wrapper.find('[data-testid="unassign-divider"]').exists()).toBe(true);
  });

  it.each`
    assignees                                                                 | expected
    ${[{ id: 5, username: '', name: '' }]}                                    | ${'Assignee'}
    ${[{ id: 6, username: '', name: '' }, { id: 7, username: '', name: '' }]} | ${'2 Assignees'}
  `(
    'when assignees have a length of $assignees.length, it renders $expected',
    ({ assignees, expected }) => {
      store.state.issues['1'].assignees = assignees;

      createComponent();

      expect(wrapper.find(BoardEditableItem).props('title')).toBe(expected);
    },
  );

  describe('when participants is loading', () => {
    beforeEach(() => {
      createComponent('', true);
    });

    it('finds a loading icon in the dropdown', () => {
      expect(findLoadingIcon().exists()).toBe(true);
    });
  });

  describe('when participants is loading is false', () => {
    beforeEach(() => {
      createComponent();
    });

    it('does not find GlLoading icon in the dropdown', () => {
      expect(findLoadingIcon().exists()).toBe(false);
    });

    it('finds at least 1 GlDropdownItem', () => {
      expect(wrapper.findAll(GlDropdownItem).length).toBeGreaterThan(0);
    });
  });

  describe('Apollo', () => {
    beforeEach(() => {
      getIssueParticipantsSpy = jest.fn().mockResolvedValue({
        data: {
          issue: {
            participants: {
              nodes: [
                {
                  username: 'participant',
                  name: 'participant',
                  webUrl: '',
                  avatarUrl: '',
                  id: '',
                },
              ],
            },
          },
        },
      });
      getSearchUsersSpy = jest.fn().mockResolvedValue({
        data: {
          users: {
            nodes: [{ username: 'root', name: 'root', webUrl: '', avatarUrl: '', id: '' }],
          },
        },
      });
    });

    describe('when search is empty', () => {
      beforeEach(() => {
        createComponentWithApollo();
      });

      it('calls getIssueParticipants', async () => {
        jest.runOnlyPendingTimers();
        await wrapper.vm.$nextTick();

        expect(getIssueParticipantsSpy).toHaveBeenCalledWith({ id: 'gid://gitlab/Issue/111' });
      });
    });

    describe('when search is not empty', () => {
      beforeEach(() => {
        createComponentWithApollo('search term');
      });

      it('calls searchUsers', async () => {
        jest.runOnlyPendingTimers();
        await wrapper.vm.$nextTick();

        expect(getSearchUsersSpy).toHaveBeenCalledWith({ search: 'search term' });
      });
    });
  });

  it('finds GlSearchBoxByType', async () => {
    createComponent();

    await openDropdown();

    expect(wrapper.find(GlSearchBoxByType).exists()).toBe(true);
  });

  describe('when assign-self is emitted from IssuableAssignees', () => {
    const currentUser = { username: 'self', name: '', id: '' };

    beforeEach(() => {
      window.gon = { current_username: currentUser.username };

      dispatchSpy.mockResolvedValue([currentUser]);
      createComponent();

      wrapper.find(IssuableAssignees).vm.$emit('assign-self');
    });

    it('calls setAssignees with currentUser', () => {
      expect(store.dispatch).toHaveBeenCalledWith('setAssignees', currentUser.username);
    });

    it('adds the user to the selected list', async () => {
      expect(findByText(currentUser.username).exists()).toBe(true);
    });
  });

  describe('when setting an assignee', () => {
    beforeEach(() => {
      createComponent();
    });

    it('passes loading state from Vuex to BoardEditableItem', async () => {
      store.state.isSettingAssignees = true;

      await wrapper.vm.$nextTick();

      expect(wrapper.find(BoardEditableItem).props('loading')).toBe(true);
    });
  });
});