import { GlCard, GlSkeletonLoading } from '@gitlab/ui';
import { nextTick } from 'vue';
import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import VulnerabilityCounts from 'ee/security_dashboard/components/shared/vulnerability_report/vulnerability_counts.vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { DASHBOARD_TYPES, SEVERITY_LEVELS } from 'ee/security_dashboard/store/constants';
import createFlash from '~/flash';
import createMockApollo from 'helpers/mock_apollo_helper';
import countsQuery from 'ee/security_dashboard/graphql/queries/vulnerability_severities_count.query.graphql';
import { SEVERITIES } from '~/vulnerabilities/constants';

jest.mock('~/flash');

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

const fullPath = 'path';
const counts = { critical: 1, high: 2, info: 3, low: 4, medium: 5, unknown: 6 };

const getCountsRequestHandler = ({
  data = counts,
  dashboardType = DASHBOARD_TYPES.PROJECT,
} = {}) => {
  return jest.fn().mockResolvedValue({
    data: {
      [dashboardType]: {
        id: '1',
        vulnerabilitySeveritiesCount: data,
      },
    },
  });
};

const defaultCountsRequestHandler = getCountsRequestHandler();

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

  const createWrapper = ({
    dashboardType = DASHBOARD_TYPES.PROJECT,
    filters = {},
    countsHandler = defaultCountsRequestHandler,
  } = {}) => {
    wrapper = mountExtended(VulnerabilityCounts, {
      localVue,
      apolloProvider: createMockApollo([[countsQuery, countsHandler]]),
      provide: {
        fullPath,
        dashboardType,
      },
      propsData: { filters },
    });
  };

  const findCards = () => wrapper.findAllComponents(GlCard);
  const findCardWithSeverity = (severity) => wrapper.findByTestId(severity);

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

  describe('vulnerability counts query', () => {
    it('calls the query once with the expected data', () => {
      const filters = { a: 1, b: 2 };
      createWrapper({ filters });

      expect(defaultCountsRequestHandler).toHaveBeenCalledTimes(1);
      expect(defaultCountsRequestHandler).toHaveBeenCalledWith(
        expect.objectContaining({ ...filters, fullPath }),
      );
    });

    it('does not call the query if filters are not ready', () => {
      createWrapper({ filters: null });

      expect(defaultCountsRequestHandler).not.toHaveBeenCalled();
    });

    it('shows an error message if the query fails', async () => {
      const countsHandler = jest.fn().mockRejectedValue(new Error());
      createWrapper({ countsHandler });
      // Have to wait 2 ticks here, one for the query to finish loading, and one more for the
      // GraphQL error handler to be called.
      await nextTick();
      await nextTick();

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

    it.each([DASHBOARD_TYPES.PROJECT, DASHBOARD_TYPES.GROUP, DASHBOARD_TYPES.INSTANCE])(
      'sets the correct variable for the %s dashboard',
      async (dashboardType) => {
        createWrapper({ dashboardType });
        await nextTick();

        expect(defaultCountsRequestHandler).toHaveBeenCalledWith(
          expect.objectContaining({
            isProject: dashboardType === DASHBOARD_TYPES.PROJECT,
            isGroup: dashboardType === DASHBOARD_TYPES.GROUP,
            isInstance: dashboardType === DASHBOARD_TYPES.INSTANCE,
          }),
        );
      },
    );
  });

  it('shows a skeleton loading component for each count when the query is loading', () => {
    createWrapper();

    findCards().wrappers.forEach((card) => {
      expect(card.findComponent(GlSkeletonLoading).exists()).toBe(true);
    });
  });

  it('shows a skeleton loading component for each count when there are no filters', () => {
    createWrapper({ filters: null });

    findCards().wrappers.forEach((card) => {
      expect(card.findComponent(GlSkeletonLoading).exists()).toBe(true);
    });
  });

  it('should show a card for each severity with the correct count', async () => {
    createWrapper();
    await nextTick();

    // Check that there are exactly the same number of cards as there are severities.
    expect(findCards()).toHaveLength(Object.keys(counts).length);

    Object.entries(counts).forEach(([severity, count]) => {
      const cardText = findCardWithSeverity(severity).text();

      expect(cardText).toContain(SEVERITY_LEVELS[severity]);
      expect(cardText).toContain(count);
    });
  });

  it('should show zero for the count when there is no value for that severity', async () => {
    const handler = getCountsRequestHandler({ data: {} });
    createWrapper({ countsHandler: handler });
    // Have to wait 2 ticks here, one for the query to finish loading, and one more for the
    // computed property to update.
    await nextTick();
    await nextTick();

    SEVERITIES.forEach((severity) => {
      expect(findCardWithSeverity(severity).text()).toContain('0');
    });
  });
});
