import { GlTable, GlLink, GlPagination, GlAlert } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { shallowMount, mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import { DEFAULT_PER_PAGE } from '~/api';
import IntegrationOverrides from '~/integrations/overrides/components/integration_overrides.vue';
import IntegrationTabs from '~/integrations/overrides/components/integration_tabs.vue';

import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';

const mockOverrides = Array(DEFAULT_PER_PAGE * 3)
  .fill(1)
  .map((_, index) => ({
    id: index,
    name: `test-proj-${index}`,
    avatar_url: `avatar-${index}`,
    full_path: `test-proj-${index}`,
    full_name: `test-proj-${index}`,
  }));

describe('IntegrationOverrides', () => {
  let wrapper;
  let mockAxios;

  const defaultProps = {
    overridesPath: 'mock/overrides',
  };

  const createComponent = ({ mountFn = shallowMount, stubs } = {}) => {
    wrapper = mountFn(IntegrationOverrides, {
      propsData: defaultProps,
      stubs,
    });
  };

  beforeEach(() => {
    mockAxios = new MockAdapter(axios);
    mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, mockOverrides, {
      'X-TOTAL': mockOverrides.length,
      'X-PAGE': 1,
    });
  });

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

  const findGlTable = () => wrapper.findComponent(GlTable);
  const findPagination = () => wrapper.findComponent(GlPagination);
  const findIntegrationTabs = () => wrapper.findComponent(IntegrationTabs);
  const findRowsAsModel = () =>
    findGlTable()
      .findAllComponents(GlLink)
      .wrappers.map((link) => {
        const avatar = link.findComponent(ProjectAvatar);

        return {
          id: avatar.props('projectId'),
          href: link.attributes('href'),
          avatarUrl: avatar.props('projectAvatarUrl'),
          avatarName: avatar.props('projectName'),
          text: link.text(),
        };
      });
  const findAlert = () => wrapper.findComponent(GlAlert);

  describe('while loading', () => {
    it('sets GlTable `busy` attribute to `true`', () => {
      createComponent();

      const table = findGlTable();
      expect(table.exists()).toBe(true);
      expect(table.attributes('busy')).toBe('true');
    });

    it('renders IntegrationTabs with count as `null`', () => {
      createComponent();

      expect(findIntegrationTabs().props('projectOverridesCount')).toBe(null);
    });
  });

  describe('when initial request is successful', () => {
    it('sets GlTable `busy` attribute to `false`', async () => {
      createComponent();
      await waitForPromises();

      const table = findGlTable();
      expect(table.exists()).toBe(true);
      expect(table.attributes('busy')).toBeUndefined();
    });

    it('renders IntegrationTabs with count', async () => {
      createComponent();
      await waitForPromises();

      expect(findIntegrationTabs().props('projectOverridesCount')).toBe(mockOverrides.length);
    });

    describe('table template', () => {
      beforeEach(async () => {
        createComponent({ mountFn: mount });
        await waitForPromises();
      });

      it('renders overrides as rows in table', () => {
        expect(findRowsAsModel()).toEqual(
          mockOverrides.map((x) => ({
            id: x.id,
            href: x.full_path,
            avatarUrl: x.avatar_url,
            avatarName: x.name,
            text: expect.stringContaining(x.full_name),
          })),
        );
      });
    });
  });

  describe('when request fails', () => {
    beforeEach(async () => {
      jest.spyOn(Sentry, 'captureException');
      mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.INTERNAL_SERVER_ERROR);

      createComponent();
      await waitForPromises();
    });

    it('displays error alert', () => {
      const alert = findAlert();
      expect(alert.exists()).toBe(true);
      expect(alert.text()).toBe(IntegrationOverrides.i18n.defaultErrorMessage);
    });

    it('hides overrides table', () => {
      const table = findGlTable();
      expect(table.exists()).toBe(false);
    });

    it('captures exception in Sentry', () => {
      expect(Sentry.captureException).toHaveBeenCalledWith(expect.any(Error));
    });
  });

  describe('pagination', () => {
    describe('when total items does not exceed the page limit', () => {
      it('does not render', async () => {
        mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, [mockOverrides[0]], {
          'X-TOTAL': DEFAULT_PER_PAGE - 1,
          'X-PAGE': 1,
        });

        createComponent();

        // wait for initial load
        await waitForPromises();

        expect(findPagination().exists()).toBe(false);
      });
    });

    describe('when total items exceeds the page limit', () => {
      const mockPage = 2;

      beforeEach(async () => {
        createComponent({ stubs: { UrlSync } });
        mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, [mockOverrides[0]], {
          'X-TOTAL': DEFAULT_PER_PAGE * 2,
          'X-PAGE': mockPage,
        });

        // wait for initial load
        await waitForPromises();
      });

      it('renders', () => {
        expect(findPagination().exists()).toBe(true);
      });

      describe('when navigating to a page', () => {
        beforeEach(async () => {
          jest.spyOn(axios, 'get');

          // trigger a page change
          await findPagination().vm.$emit('input', mockPage);
        });

        it('performs GET request with correct params', () => {
          expect(axios.get).toHaveBeenCalledWith(defaultProps.overridesPath, {
            params: { page: mockPage, per_page: DEFAULT_PER_PAGE },
          });
        });

        it('updates `page` URL parameter', () => {
          expect(window.location.search).toBe(`?page=${mockPage}`);
        });
      });
    });
  });
});