import MockAdapter from 'axios-mock-adapter';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlEmptyState } from '@gitlab/ui';
import Project from 'ee/operations/components/dashboard/project.vue';
import Dashboard from 'ee/operations/components/dashboard/dashboard.vue';
import createStore from 'ee/vue_shared/dashboards/store';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import { mockProjectData, mockText } from '../../mock_data';

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

describe('dashboard component', () => {
  const mockAddEndpoint = 'mock-addPath';
  const mockListEndpoint = 'mock-listPath';
  const store = createStore();
  let wrapper;
  let mockAxios;

  const emptyDashboardHelpPath = '/help/user/operations_dashboard/index.html';
  const emptyDashboardSvgPath = '/assets/illustrations/operations-dashboard_empty.svg';

  const mountComponent = ({ stubs = {}, state = {} } = {}) =>
    mount(Dashboard, {
      store,
      localVue,
      propsData: {
        addPath: mockAddEndpoint,
        listPath: mockListEndpoint,
        emptyDashboardSvgPath,
        emptyDashboardHelpPath,
      },
      state,
      stubs,
    });

  const findEmptyState = () => wrapper.find(GlEmptyState);
  const findAddProjectButton = () => wrapper.find('[data-testid=add-projects-button]');

  beforeEach(() => {
    mockAxios = new MockAdapter(axios);
    mockAxios.onGet(mockListEndpoint).replyOnce(200, { projects: mockProjectData(1) });
    wrapper = mountComponent();
  });

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

  it('renders dashboard title', () => {
    const dashboardTitle = wrapper.element.querySelector('.js-dashboard-title');

    expect(dashboardTitle.innerText.trim()).toEqual(mockText.DASHBOARD_TITLE);
  });

  describe('add projects button', () => {
    let button;

    beforeEach(() => {
      button = findAddProjectButton();
    });

    it('renders add projects text', () => {
      expect(button.text()).toBe(mockText.ADD_PROJECTS);
    });

    describe('when a project is added', () => {
      it('immediately requests the project list again', () => {
        mockAxios.reset();
        mockAxios.onGet(mockListEndpoint).replyOnce(200, { projects: mockProjectData(2) });
        mockAxios.onPost(mockAddEndpoint).replyOnce(200, { added: [1], invalid: [] });

        return wrapper.vm
          .$nextTick()
          .then(() => {
            wrapper.vm.projectClicked({ id: 1 });
          })
          .then(waitForPromises)
          .then(() => {
            wrapper.vm.onOk();
          })
          .then(waitForPromises)
          .then(() => {
            expect(store.state.projects.length).toEqual(2);
            expect(wrapper.findAll(Project).length).toEqual(2);
          });
      });
    });
  });

  describe('wrapped components', () => {
    describe('dashboard project component', () => {
      const projectCount = 1;

      beforeEach(() => {
        const projects = mockProjectData(projectCount);
        store.state.projects = projects;
        wrapper = mountComponent();
      });

      it('includes a dashboard project component for each project', () => {
        const projectComponents = wrapper.findAll(Project);

        expect(projectComponents).toHaveLength(projectCount);
      });

      it('passes each project to the dashboard project component', () => {
        const [oneProject] = store.state.projects;
        const projectComponent = wrapper.find(Project);

        expect(projectComponent.props().project).toEqual(oneProject);
      });

      it('dispatches setProjects when projects changes', () => {
        const dispatch = jest.spyOn(wrapper.vm.$store, 'dispatch').mockImplementation(() => {});
        const projects = mockProjectData(3);

        wrapper.vm.projects = projects;

        expect(dispatch).toHaveBeenCalledWith('setProjects', projects);
      });

      describe('when a project is removed', () => {
        it('immediately requests the project list again', () => {
          mockAxios.reset();
          mockAxios.onDelete(store.state.projects[0].remove_path).reply(200);
          mockAxios.onGet(mockListEndpoint).replyOnce(200, { projects: [] });

          wrapper.find('button.js-remove-button').vm.$emit('click');

          return waitForPromises().then(() => {
            expect(store.state.projects.length).toEqual(0);
            expect(wrapper.findAll(Project).length).toEqual(0);
          });
        });
      });
    });

    describe('add projects modal', () => {
      beforeEach(() => {
        store.state.projectSearchResults = mockProjectData(2);
        store.state.selectedProjects = mockProjectData(1);
      });

      it('clears state when adding a valid project', () => {
        mockAxios.onPost(mockAddEndpoint).replyOnce(200, { added: [1], invalid: [] });

        return wrapper.vm
          .$nextTick()
          .then(() => {
            wrapper.vm.onOk();
          })
          .then(waitForPromises)
          .then(() => {
            expect(store.state.projectSearchResults).toHaveLength(0);
            expect(store.state.selectedProjects).toHaveLength(0);
          });
      });

      it('clears state when adding an invalid project', () => {
        mockAxios.onPost(mockAddEndpoint).replyOnce(200, { added: [], invalid: [1] });

        return wrapper.vm
          .$nextTick()
          .then(() => {
            wrapper.vm.onOk();
          })
          .then(waitForPromises)
          .then(() => {
            expect(store.state.projectSearchResults).toHaveLength(0);
            expect(store.state.selectedProjects).toHaveLength(0);
          });
      });

      it('clears state when canceled', () => {
        return wrapper.vm
          .$nextTick()
          .then(() => {
            wrapper.vm.onCancel();
          })
          .then(waitForPromises)
          .then(() => {
            expect(store.state.projectSearchResults).toHaveLength(0);
            expect(store.state.selectedProjects).toHaveLength(0);
          });
      });

      it('clears state on error', () => {
        mockAxios.onPost(mockAddEndpoint).replyOnce(500, {});

        return wrapper.vm
          .$nextTick()
          .then(() => {
            expect(store.state.projectSearchResults.length).not.toBe(0);
            expect(store.state.selectedProjects.length).not.toBe(0);
            wrapper.vm.onOk();
          })
          .then(waitForPromises)
          .then(() => {
            expect(store.state.projectSearchResults).toHaveLength(0);
            expect(store.state.selectedProjects).toHaveLength(0);
          });
      });
    });

    describe('when no projects have been added', () => {
      beforeEach(() => {
        store.state.projects = [];
        store.state.isLoadingProjects = false;
      });

      it('should render the empty state', () => {
        expect(findEmptyState().exists()).toBe(true);
      });

      it('should link to the documentation', () => {
        const link = findEmptyState().find('[data-testid="documentation-link"]');

        expect(link.exists()).toBe(true);
        expect(link.attributes().href).toEqual(emptyDashboardHelpPath);
      });

      it('should render the add projects button', () => {
        const button = findAddProjectButton();

        expect(button.exists()).toBe(true);
        expect(button.text()).toEqual('Add projects');
      });
    });
  });
});
