import VueRouter from 'vue-router';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { initFirstClassVulnerabilityFilters } from 'ee/security_dashboard/helpers';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import Filter from 'ee/security_dashboard/components/filter.vue';

const router = new VueRouter();
const localVue = createLocalVue();
localVue.use(VueRouter);

describe('First class vulnerability filters component', () => {
  let wrapper;
  let filters;

  const projects = [
    { id: 'gid://gitlab/Project/11', name: 'GitLab Org' },
    { id: 'gid://gitlab/Project/12', name: 'GitLab Com' },
  ];

  const findFilters = () => wrapper.findAll(Filter);
  const findStateFilter = () => findFilters().at(0);
  const findSeverityFilter = () => findFilters().at(1);
  const findReportTypeFilter = () => findFilters().at(2);
  const findProjectFilter = () => findFilters().at(3);
  const findLastFilter = () => findFilters().at(filters.length - 1);

  const createComponent = ({ propsData, listeners } = {}) => {
    return shallowMount(Filters, { localVue, router, propsData, listeners });
  };

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

  describe('on render without project filter', () => {
    beforeEach(() => {
      wrapper = createComponent();
      filters = initFirstClassVulnerabilityFilters();
    });

    it('should render the filters', () => {
      expect(findFilters()).toHaveLength(filters.length);
    });

    it('should call the setFilter mutation when setting a filter', () => {
      const stub = jest.fn();
      const options = { foo: 'bar' };

      wrapper.setMethods({ setFilter: stub });
      findStateFilter().vm.$emit('setFilter', options);

      expect(stub).toHaveBeenCalledWith(options);
    });
  });

  describe('when project filter is populated dynamically', () => {
    beforeEach(() => {
      filters = initFirstClassVulnerabilityFilters([]);
      wrapper = createComponent({ propsData: { projects: [] } });
    });

    it('should render the project filter with one option', () => {
      expect(findLastFilter().props('filter')).toEqual({
        id: 'projectId',
        name: 'Project',
        options: [{ id: 'all', name: 'All projects' }],
        selection: new Set(['all']),
      });
    });

    it('should set the projects dynamically', () => {
      wrapper.setProps({ projects });
      return wrapper.vm.$nextTick(() => {
        expect(findLastFilter().props('filter')).toEqual(
          expect.objectContaining({
            options: [
              { id: 'all', name: 'All projects' },
              { id: '11', name: 'GitLab Org' },
              { id: '12', name: 'GitLab Com' },
            ],
          }),
        );
      });
    });
  });

  describe('when project filter is ready on mount', () => {
    beforeEach(() => {
      filters = initFirstClassVulnerabilityFilters([]);
      wrapper = createComponent({ propsData: { projects } });
    });

    it('should set the projects dynamically', () => {
      expect(findLastFilter().props('filter')).toEqual(
        expect.objectContaining({
          options: [
            { id: 'all', name: 'All projects' },
            { id: '11', name: 'GitLab Org' },
            { id: '12', name: 'GitLab Com' },
          ],
        }),
      );
    });
  });

  describe('when no filter is persisted in the URL', () => {
    beforeEach(() => {
      wrapper = createComponent({
        propsData: { projects },
      });
    });

    it('should redirect the user to an updated the URL and default the filters to CONFIRMED + DETECTED state', () => {
      expect(findStateFilter().props('filter')).toEqual(
        expect.objectContaining({
          selection: new Set(['DETECTED', 'CONFIRMED']),
        }),
      );
    });
  });

  describe.each`
    filter          | value                   | selector
    ${'state'}      | ${'DETECTED,DISMISSED'} | ${findStateFilter}
    ${'severity'}   | ${'MEDIUM'}             | ${findSeverityFilter}
    ${'reportType'} | ${'SAST'}               | ${findReportTypeFilter}
    ${'projectId'}  | ${'12'}                 | ${findProjectFilter}
  `('when filters are persisted', ({ filter, value, selector }) => {
    describe(`with filter set to ${filter}: ${value}`, () => {
      let filterChangeSpy;

      beforeEach(() => {
        filterChangeSpy = jest.fn();

        wrapper = createComponent({
          propsData: { projects },
          listeners: { filterChange: filterChangeSpy },
        });
        // reset the router query in-between test cases
        router.push({ query: {} });
        router.push({ query: { [filter]: value.split(',') } }, () => {});
      });

      it(`should have the ${filter} filter as pre-selected`, () => {
        expect(selector().props('filter').selection).toEqual(new Set(value.split(',')));
      });

      it('should emit a filterChange event', () => {
        expect(wrapper.emitted().filterChange).toBeTruthy();
      });

      it('should not trigger the filterChange additonally when the filters do not change', () => {
        router.push({
          query: {
            ...wrapper.vm.$route.query,
            'some-unrelated-query-param': 'true',
          },
        });

        return wrapper.vm.$nextTick(() => {
          expect(filterChangeSpy).toHaveBeenCalledTimes(1);
        });
      });

      it('should trigger the filterChange when the filters are reset', () => {
        router.push({ query: {} });

        return wrapper.vm.$nextTick(() => {
          expect(filterChangeSpy).toHaveBeenNthCalledWith(2, {});
        });
      });

      it('should reset the filters when the URL contains no more filters', () => {
        router.push({ query: {} });

        return wrapper.vm.$nextTick(() => {
          expect(selector().props('filter').selection).toEqual(new Set(['all']));
        });
      });
    });
  });

  describe.each`
    filter          | selector                | index
    ${'state'}      | ${findStateFilter}      | ${0}
    ${'severity'}   | ${findSeverityFilter}   | ${1}
    ${'reportType'} | ${findReportTypeFilter} | ${2}
    ${'projectId'}  | ${findProjectFilter}    | ${3}
  `('when setFilter is called', ({ filter, selector, index }) => {
    describe(filter, () => {
      let filterId;
      let optionId;
      let routePushSpy;

      beforeEach(() => {
        filters = initFirstClassVulnerabilityFilters(projects);
        filterId = filters[index].id;
        optionId = filters[index].options[1].id;
        wrapper = createComponent({ propsData: { projects } });
        routePushSpy = jest.spyOn(router, 'push');
        selector().vm.$emit('setFilter', { optionId, filterId });
      });

      afterEach(() => {
        // This will reset the query state
        router.push('/');
      });

      it('should set the filter', () => {
        expect(selector().props('filter').selection).toEqual(new Set([optionId]));
      });

      it('should emit a filterChange event', () => {
        expect(wrapper.emitted().filterChange).toBeTruthy();
      });

      it('should update the path', () => {
        expect(routePushSpy).toHaveBeenCalledWith({
          query: { [filterId]: [optionId] },
        });
      });
    });
  });
});
