import { GlAlert } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { merge } from 'lodash';
import ConfigurationForm from 'ee/security_configuration/api_fuzzing/components/configuration_form.vue';
import { SCAN_MODES } from 'ee/security_configuration/api_fuzzing/constants';
import ConfigurationSnippetModal from 'ee/security_configuration/components/configuration_snippet_modal.vue';
import { CONFIGURATION_SNIPPET_MODAL_ID } from 'ee/security_configuration/components/constants';
import DropdownInput from 'ee/security_configuration/components/dropdown_input.vue';
import DynamicFields from 'ee/security_configuration/components/dynamic_fields.vue';
import FormInput from 'ee/security_configuration/components/form_input.vue';
import { stripTypenames } from 'helpers/graphql_helpers';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { CODE_SNIPPET_SOURCE_API_FUZZING } from '~/pipeline_editor/components/code_snippet_alert/constants';
import {
  apiFuzzingConfigurationQueryResponse,
  createApiFuzzingConfigurationMutationResponse,
} from '../mock_data';

describe('EE - ApiFuzzingConfigurationForm', () => {
  let wrapper;

  const apiFuzzingCiConfiguration = stripTypenames(
    apiFuzzingConfigurationQueryResponse.data.project.apiFuzzingCiConfiguration,
  );

  const findAlert = () => wrapper.find(GlAlert);
  const findEnableAuthenticationCheckbox = () =>
    wrapper.findByTestId('api-fuzzing-enable-authentication-checkbox');
  const findTargetUrlInput = () => wrapper.findAll(FormInput).at(0);
  const findScanModeInput = () => wrapper.findAll(DropdownInput).at(0);
  const findSpecificationFileInput = () => wrapper.findAll(FormInput).at(1);
  const findAuthenticationNotice = () => wrapper.findByTestId('api-fuzzing-authentication-notice');
  const findAuthenticationFields = () => wrapper.find(DynamicFields);
  const findScanProfileDropdownInput = () => wrapper.findAll(DropdownInput).at(1);
  const findScanProfileYamlViewer = () =>
    wrapper.findByTestId('api-fuzzing-scan-profile-yaml-viewer');
  const findSubmitButton = () => wrapper.findByTestId('api-fuzzing-configuration-submit-button');
  const findCancelButton = () => wrapper.findByTestId('api-fuzzing-configuration-cancel-button');
  const findConfigurationSnippetModal = () => wrapper.find(ConfigurationSnippetModal);

  const setFormData = async () => {
    findTargetUrlInput().vm.$emit('input', 'https://gitlab.com');
    await findScanModeInput().vm.$emit('input', 'HAR');
    findSpecificationFileInput().vm.$emit('input', '/specification/file/path');
    return findScanProfileDropdownInput().vm.$emit(
      'input',
      apiFuzzingCiConfiguration.scanProfiles[0].name,
    );
  };

  const createWrapper = (options = {}) => {
    wrapper = extendedWrapper(
      mount(
        ConfigurationForm,
        merge(
          {
            provide: {
              fullPath: 'namespace/project',
              securityConfigurationPath: '/security/configuration',
              apiFuzzingAuthenticationDocumentationPath:
                'api_fuzzing_authentication/documentation/path',
              ciVariablesDocumentationPath: '/ci_cd_variables/documentation/path',
              projectCiSettingsPath: '/project/settings/ci_cd',
              canSetProjectCiVariables: true,
            },
            propsData: {
              apiFuzzingCiConfiguration,
            },
            mocks: {
              $apollo: {
                mutate: jest.fn(),
              },
            },
          },
          options,
        ),
      ),
    );
  };

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

  it('includes a link to API fuzzing authentication documentation', () => {
    createWrapper();

    expect(wrapper.html()).toContain('api_fuzzing_authentication/documentation/path');
  });

  describe('scan modes', () => {
    beforeEach(() => {
      createWrapper();
    });

    it('displays a dropdown option for each scan mode', () => {
      findScanModeInput()
        .findAll('li')
        .wrappers.forEach((item, index) => {
          expect(item.text()).toBe(
            SCAN_MODES[apiFuzzingCiConfiguration.scanModes[index]].scanModeLabel,
          );
        });
    });

    it('by default, the specification file input is hidden', () => {
      expect(wrapper.findAll(FormInput)).toHaveLength(1);
    });

    describe.each(Object.keys(SCAN_MODES))('when %s scan mode is selected', (scanMode) => {
      it('the specificationfile input becomes visible and has the correct labels', async () => {
        const selectedScanMode = SCAN_MODES[scanMode];
        findScanModeInput().vm.$emit('input', scanMode);
        await wrapper.vm.$nextTick();

        const specificationFileInput = findSpecificationFileInput();
        expect(specificationFileInput.exists()).toBe(true);
        expect(specificationFileInput.text()).toContain(selectedScanMode.label);
        expect(specificationFileInput.text()).toContain(selectedScanMode.description);
        expect(specificationFileInput.find('input').attributes('placeholder')).toBe(
          selectedScanMode.placeholder,
        );
      });
    });
  });

  describe('authentication', () => {
    it('authentication section is hidden by default', () => {
      createWrapper();

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

    it('authentication section becomes visible once checkbox is checked', async () => {
      createWrapper();
      await findEnableAuthenticationCheckbox().trigger('click');

      expect(findAuthenticationNotice().exists()).toBe(true);
    });

    it('sees the the proper notice as a maintainer', async () => {
      createWrapper();
      await findEnableAuthenticationCheckbox().trigger('click');

      expect(findAuthenticationNotice().text()).toMatchInterpolatedText(
        'Make sure your credentials are secured To prevent a security leak, authentication info must be added as a CI variable. As a user with maintainer access rights, you can manage CI variables in the Settings area.',
      );
    });

    it('sees the the proper notice as a developer', async () => {
      createWrapper({
        provide: {
          canSetProjectCiVariables: false,
        },
      });
      await findEnableAuthenticationCheckbox().trigger('click');

      expect(findAuthenticationNotice().text()).toMatchInterpolatedText(
        "You may need a maintainer's help to secure your credentials. To prevent a security leak, authentication info must be added as a CI variable. A user with maintainer access rights can manage CI variables in the Settings area. We detected that you are not a maintainer. Commit your changes and assign them to a maintainer to update the credentials before merging.",
      );
    });
  });

  describe('scan profiles', () => {
    beforeEach(() => {
      createWrapper();
    });

    it('displays a dropdown option for each scan profile', () => {
      const dropdownItems = findScanProfileDropdownInput().findAll('li').wrappers;
      dropdownItems.shift(); // Skip section header
      dropdownItems.forEach((item, index) => {
        expect(item.text()).toBe(apiFuzzingCiConfiguration.scanProfiles[index].description);
      });
    });

    it('by default, YAML viewer is not visible', () => {
      expect(findScanProfileYamlViewer().exists()).toBe(false);
    });

    it('when a scan profile is selected, its YAML is visible', async () => {
      const [selectedScanProfile] = apiFuzzingCiConfiguration.scanProfiles;
      findScanProfileDropdownInput().vm.$emit('input', selectedScanProfile.name);
      await wrapper.vm.$nextTick();

      expect(findScanProfileYamlViewer().exists()).toBe(true);
      expect(findScanProfileYamlViewer().text()).toBe(selectedScanProfile.yaml.trim());
    });
  });

  describe('form submission', () => {
    it('cancel button points to Security Configuration page', () => {
      createWrapper();

      expect(findCancelButton().attributes('href')).toBe('/security/configuration');
    });

    it('submit button is disabled until all fields are filled', async () => {
      createWrapper();

      expect(findSubmitButton().props('disabled')).toBe(true);

      await setFormData();

      expect(findSubmitButton().props('disabled')).toBe(false);

      await findEnableAuthenticationCheckbox().trigger('click');

      expect(findSubmitButton().props('disabled')).toBe(true);

      await findAuthenticationFields().vm.$emit('input', [
        {
          ...wrapper.vm.authenticationSettings[0],
          value: '$UsernameVariable',
        },
        {
          ...wrapper.vm.authenticationSettings[1],
          value: '$PasswordVariable',
        },
      ]);

      expect(findSubmitButton().props('disabled')).toBe(false);
    });

    it('triggers the createApiFuzzingConfiguration mutation on submit and opens the modal with the correct props', async () => {
      createWrapper();
      jest
        .spyOn(wrapper.vm.$apollo, 'mutate')
        .mockResolvedValue(createApiFuzzingConfigurationMutationResponse);
      jest.spyOn(wrapper.vm.$refs[CONFIGURATION_SNIPPET_MODAL_ID], 'show');
      await setFormData();
      wrapper.find('form').trigger('submit');
      await waitForPromises();

      expect(findAlert().exists()).toBe(false);
      expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
      expect(wrapper.vm.$refs[CONFIGURATION_SNIPPET_MODAL_ID].show).toHaveBeenCalled();
      expect(findConfigurationSnippetModal().props()).toEqual({
        ciYamlEditUrl:
          createApiFuzzingConfigurationMutationResponse.data.apiFuzzingCiConfigurationCreate
            .gitlabCiYamlEditPath,
        yaml: `---
# Tip: Insert this part below all stages
stages:
- fuzz
# Tip: Insert this part below all include
include:
- template: template.gitlab-ci.yml
# Tip: Insert the following variables anywhere below stages and include
variables:
- FOO: bar`,
        redirectParam: CODE_SNIPPET_SOURCE_API_FUZZING,
        scanType: 'API Fuzzing',
      });
    });

    it('shows an error on top-level error', async () => {
      createWrapper({
        mocks: {
          $apollo: {
            mutate: jest.fn().mockRejectedValue(),
          },
        },
      });
      await setFormData();

      expect(findAlert().exists()).toBe(false);

      wrapper.find('form').trigger('submit');
      await waitForPromises();

      expect(findAlert().exists()).toBe(true);
      expect(window.scrollTo).toHaveBeenCalledWith({ top: 0 });
    });

    it('shows an error on error-as-data', async () => {
      createWrapper({
        mocks: {
          $apollo: {
            mutate: jest.fn().mockResolvedValue({
              data: {
                apiFuzzingCiConfigurationCreate: {
                  errors: ['error#1'],
                },
              },
            }),
          },
        },
      });
      await setFormData();

      expect(findAlert().exists()).toBe(false);

      wrapper.find('form').trigger('submit');
      await waitForPromises();

      expect(findAlert().exists()).toBe(true);
      expect(window.scrollTo).toHaveBeenCalledWith({ top: 0 });
    });
  });
});
