import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import fetchHeaderVulnerabilityQuery from 'ee/security_dashboard/graphql/header_vulnerability.graphql';
import FalsePositiveAlert from 'ee/vulnerabilities/components/false_positive_alert.vue';
import VulnerabilityFooter from 'ee/vulnerabilities/components/footer.vue';
import VulnerabilityHeader from 'ee/vulnerabilities/components/header.vue';
import Main from 'ee/vulnerabilities/components/vulnerability.vue';
import Details from 'ee/vulnerabilities/components/vulnerability_details.vue';
import { stubComponent } from 'helpers/stub_component';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';

const localVue = createLocalVue();
localVue.use(VueApollo);
jest.mock('~/flash');

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

  const getVulnerability = (props) => ({
    id: 1,
    created_at: new Date().toISOString(),
    report_type: 'sast',
    state: 'detected',
    create_mr_url: '/create_mr_url',
    new_issue_url: '/new_issue_url',
    project_fingerprint: 'abc123',
    pipeline: {
      id: 2,
      created_at: new Date().toISOString(),
      url: 'pipeline_url',
      sourceBranch: 'main',
    },
    description: 'description',
    identifiers: 'identifiers',
    links: 'links',
    location: 'location',
    name: 'name',
    project: {
      full_path: '/project_full_path',
      full_name: 'Test Project',
    },
    discussions_url: '/discussion_url',
    notes_url: '/notes_url',
    can_modify_related_issues: false,
    related_issues_help_path: '/help_path',
    merge_request_feedback: null,
    issue_feedback: null,
    remediation: null,
    ...props,
  });

  const createApolloProvider = (...queries) => {
    return createMockApollo([...queries]);
  };

  const createApolloProviderForVulnerabilityStateChange = () => {
    return createApolloProvider([
      fetchHeaderVulnerabilityQuery,
      jest.fn().mockResolvedValue({
        data: {
          errors: [],
          vulnerability: {
            id: 'gid://gitlab/Vulnerability/54',
            resolvedAt: '2020-09-16T11:13:26Z',
            state: 'RESOLVED',
          },
        },
      }),
    ]);
  };

  const createWrapper = ({ vulnData, apolloProvider } = {}) => {
    wrapper = shallowMount(Main, {
      localVue,
      apolloProvider,
      propsData: {
        initialVulnerability: { ...getVulnerability(), ...vulnData },
      },
      stubs: {
        VulnerabilityFooter: stubComponent(VulnerabilityFooter),
      },
    });
  };

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

  const findHeader = () => wrapper.findComponent(VulnerabilityHeader);
  const findDetails = () => wrapper.findComponent(Details);
  const findFooter = () => wrapper.findComponent(VulnerabilityFooter);
  const findAlert = () => wrapper.findComponent(FalsePositiveAlert);

  describe('default behavior', () => {
    beforeEach(() => {
      createWrapper({ apolloProvider: createApolloProviderForVulnerabilityStateChange() });
    });

    it('consists of header, details, and footer', () => {
      expect(findHeader().exists()).toBe(true);
      expect(findDetails().exists()).toBe(true);
      expect(findFooter().exists()).toBe(true);
    });

    it('passes the correct properties to the children', () => {
      const vulnerability = getVulnerability();
      expect(findHeader().props('vulnerability')).toEqual(vulnerability);
      expect(findDetails().props('vulnerability')).toEqual(vulnerability);
      expect(findFooter().props('vulnerability')).toEqual(vulnerability);
    });
  });

  describe('vulnerability state change event', () => {
    let makeRequest;
    let refreshVulnerability;

    beforeEach(() => {
      createWrapper({ apolloProvider: createApolloProviderForVulnerabilityStateChange() });
      refreshVulnerability = jest.spyOn(wrapper.vm.$apollo.queries.vulnerability, 'refetch');
      makeRequest = jest.spyOn(findFooter().vm, 'fetchDiscussions');
    });

    it('updates the footer notes when the vulnerbility state was changed', () => {
      findHeader().vm.$emit('vulnerability-state-change', getVulnerability());

      expect(makeRequest).toHaveBeenCalledTimes(1);
      expect(refreshVulnerability).not.toHaveBeenCalled();
    });

    it('updates the header when the footer received a state-change note', async () => {
      findFooter().vm.$emit('vulnerability-state-change');

      expect(makeRequest).not.toHaveBeenCalled();
      expect(refreshVulnerability).toHaveBeenCalledTimes(1);
      await waitForPromises();
      expect(findHeader().props('vulnerability')).toEqual(
        getVulnerability({ id: 54, state: 'resolved', resolvedAt: '2020-09-16T11:13:26Z' }),
      );
    });
  });

  describe('with false positive', () => {
    it('renders false positive alert', () => {
      createWrapper({
        vulnData: { falsePositive: true },
      });
      expect(findAlert().exists()).toBe(true);
    });
  });

  describe('refresh vulnerability', () => {
    describe('on success', () => {
      beforeEach(() => {
        createWrapper({
          apolloProvider: createApolloProviderForVulnerabilityStateChange(),
          vulnerability: getVulnerability({}),
        });
      });
    });

    describe('on failure', () => {
      beforeEach(() => {
        const apolloProvider = createApolloProvider([
          fetchHeaderVulnerabilityQuery,
          jest.fn().mockRejectedValue({
            data: {
              errors: [{ message: 'something went wrong while fetching the vulnerability' }],
              vulnerability: null,
            },
          }),
        ]);

        createWrapper({ apolloProvider });
      });

      it('calls createFlash', async () => {
        findFooter().vm.$emit('vulnerability-state-change');
        await waitForPromises();
        expect(createFlash).toHaveBeenCalledTimes(1);
      });
    });
  });
});
