import { GlEmptyState } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import stubChildren from 'helpers/stub_children';

import AdditionalMetadata from '~/packages/details/components/additional_metadata.vue';
import PackagesApp from '~/packages/details/components/app.vue';
import DependencyRow from '~/packages/details/components/dependency_row.vue';
import InstallationCommands from '~/packages/details/components/installation_commands.vue';
import PackageFiles from '~/packages/details/components/package_files.vue';
import PackageHistory from '~/packages/details/components/package_history.vue';
import PackageTitle from '~/packages/details/components/package_title.vue';
import * as getters from '~/packages/details/store/getters';
import PackageListRow from '~/packages/shared/components/package_list_row.vue';
import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
import { TrackingActions } from '~/packages/shared/constants';
import * as SharedUtils from '~/packages/shared/utils';
import Tracking from '~/tracking';

import {
  composerPackage,
  conanPackage,
  mavenPackage,
  mavenFiles,
  npmPackage,
  nugetPackage,
} from '../../mock_data';

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

useMockLocationHelper();

describe('PackagesApp', () => {
  let wrapper;
  let store;
  const fetchPackageVersions = jest.fn();
  const deletePackage = jest.fn();
  const deletePackageFile = jest.fn();
  const defaultProjectName = 'bar';

  function createComponent({
    packageEntity = mavenPackage,
    packageFiles = mavenFiles,
    isLoading = false,
    projectName = defaultProjectName,
  } = {}) {
    store = new Vuex.Store({
      state: {
        isLoading,
        packageEntity,
        packageFiles,
        canDelete: true,
        emptySvgPath: 'empty-illustration',
        npmPath: 'foo',
        npmHelpPath: 'foo',
        projectName,
        projectListUrl: 'project_url',
        groupListUrl: 'group_url',
      },
      actions: {
        deletePackage,
        fetchPackageVersions,
        deletePackageFile,
      },
      getters,
    });

    wrapper = mount(PackagesApp, {
      localVue,
      store,
      stubs: {
        ...stubChildren(PackagesApp),
        PackageTitle: false,
        TitleArea: false,
        GlButton: false,
        GlModal: false,
        GlTab: false,
        GlTabs: false,
        GlTable: false,
      },
    });
  }

  const packageTitle = () => wrapper.find(PackageTitle);
  const emptyState = () => wrapper.find(GlEmptyState);
  const deleteButton = () => wrapper.find('.js-delete-button');
  const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' });
  const findDeleteFileModal = () => wrapper.find({ ref: 'deleteFileModal' });
  const versionsTab = () => wrapper.find('.js-versions-tab > a');
  const packagesLoader = () => wrapper.find(PackagesListLoader);
  const packagesVersionRows = () => wrapper.findAll(PackageListRow);
  const noVersionsMessage = () => wrapper.find('[data-testid="no-versions-message"]');
  const dependenciesTab = () => wrapper.find('.js-dependencies-tab > a');
  const dependenciesCountBadge = () => wrapper.find('[data-testid="dependencies-badge"]');
  const noDependenciesMessage = () => wrapper.find('[data-testid="no-dependencies-message"]');
  const dependencyRows = () => wrapper.findAll(DependencyRow);
  const findPackageHistory = () => wrapper.find(PackageHistory);
  const findAdditionalMetadata = () => wrapper.find(AdditionalMetadata);
  const findInstallationCommands = () => wrapper.find(InstallationCommands);
  const findPackageFiles = () => wrapper.find(PackageFiles);

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

  it('renders the app and displays the package title', async () => {
    createComponent();

    await nextTick();

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

  it('renders an empty state component when no an invalid package is passed as a prop', () => {
    createComponent({
      packageEntity: {},
    });

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

  it('package history has the right props', () => {
    createComponent();
    expect(findPackageHistory().exists()).toBe(true);
    expect(findPackageHistory().props('packageEntity')).toEqual(wrapper.vm.packageEntity);
    expect(findPackageHistory().props('projectName')).toEqual(wrapper.vm.projectName);
  });

  it('additional metadata has the right props', () => {
    createComponent();
    expect(findAdditionalMetadata().exists()).toBe(true);
    expect(findAdditionalMetadata().props('packageEntity')).toEqual(wrapper.vm.packageEntity);
  });

  it('installation commands has the right props', () => {
    createComponent();
    expect(findInstallationCommands().exists()).toBe(true);
    expect(findInstallationCommands().props('packageEntity')).toEqual(wrapper.vm.packageEntity);
  });

  it('hides the files table if package type is COMPOSER', () => {
    createComponent({ packageEntity: composerPackage });
    expect(findPackageFiles().exists()).toBe(false);
  });

  describe('deleting packages', () => {
    beforeEach(() => {
      createComponent();
      deleteButton().trigger('click');
    });

    it('shows the delete confirmation modal when delete is clicked', () => {
      expect(findDeleteModal().exists()).toBe(true);
    });
  });

  describe('deleting package files', () => {
    it('shows the delete confirmation modal when delete is clicked', () => {
      createComponent();
      findPackageFiles().vm.$emit('delete-file', mavenFiles[0]);

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

  describe('versions', () => {
    describe('api call', () => {
      beforeEach(() => {
        createComponent();
      });

      it('makes api request on first click of tab', () => {
        versionsTab().trigger('click');

        expect(fetchPackageVersions).toHaveBeenCalled();
      });
    });

    it('displays the loader when state is loading', () => {
      createComponent({ isLoading: true });

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

    it('displays the correct version count when the package has versions', () => {
      createComponent({ packageEntity: npmPackage });

      expect(packagesVersionRows()).toHaveLength(npmPackage.versions.length);
    });

    it('displays the no versions message when there are none', () => {
      createComponent();

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

  describe('dependency links', () => {
    it('does not show the dependency links for a non nuget package', () => {
      createComponent();

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

    it('shows the dependencies tab with 0 count when a nuget package with no dependencies', () => {
      createComponent({
        packageEntity: {
          ...nugetPackage,
          dependency_links: [],
        },
      });

      return wrapper.vm.$nextTick(() => {
        const dependenciesBadge = dependenciesCountBadge();

        expect(dependenciesTab().exists()).toBe(true);
        expect(dependenciesBadge.exists()).toBe(true);
        expect(dependenciesBadge.text()).toBe('0');
        expect(noDependenciesMessage().exists()).toBe(true);
      });
    });

    it('renders the correct number of dependency rows for a nuget package', () => {
      createComponent({ packageEntity: nugetPackage });

      return wrapper.vm.$nextTick(() => {
        const dependenciesBadge = dependenciesCountBadge();

        expect(dependenciesTab().exists()).toBe(true);
        expect(dependenciesBadge.exists()).toBe(true);
        expect(dependenciesBadge.text()).toBe(nugetPackage.dependency_links.length.toString());
        expect(dependencyRows()).toHaveLength(nugetPackage.dependency_links.length);
      });
    });
  });

  describe('tracking and delete', () => {
    describe('delete package', () => {
      const originalReferrer = document.referrer;
      const setReferrer = (value = defaultProjectName) => {
        Object.defineProperty(document, 'referrer', {
          value,
          configurable: true,
        });
      };

      afterEach(() => {
        Object.defineProperty(document, 'referrer', {
          value: originalReferrer,
          configurable: true,
        });
      });

      it('calls the proper vuex action', () => {
        createComponent({ packageEntity: npmPackage });
        findDeleteModal().vm.$emit('primary');
        expect(deletePackage).toHaveBeenCalled();
      });

      it('when referrer contains project name calls window.replace with project url', async () => {
        setReferrer();
        deletePackage.mockResolvedValue();
        createComponent({ packageEntity: npmPackage });
        findDeleteModal().vm.$emit('primary');
        await deletePackage();
        expect(window.location.replace).toHaveBeenCalledWith(
          'project_url?showSuccessDeleteAlert=true',
        );
      });

      it('when referrer does not contain project name calls window.replace with group url', async () => {
        setReferrer('baz');
        deletePackage.mockResolvedValue();
        createComponent({ packageEntity: npmPackage });
        findDeleteModal().vm.$emit('primary');
        await deletePackage();
        expect(window.location.replace).toHaveBeenCalledWith(
          'group_url?showSuccessDeleteAlert=true',
        );
      });
    });

    describe('delete file', () => {
      it('calls the proper vuex action', () => {
        createComponent({ packageEntity: npmPackage });

        findPackageFiles().vm.$emit('delete-file', mavenFiles[0]);
        findDeleteFileModal().vm.$emit('primary');

        expect(deletePackageFile).toHaveBeenCalled();
      });
    });

    describe('tracking', () => {
      let eventSpy;
      let utilSpy;
      const category = 'foo';

      beforeEach(() => {
        eventSpy = jest.spyOn(Tracking, 'event');
        utilSpy = jest.spyOn(SharedUtils, 'packageTypeToTrackCategory').mockReturnValue(category);
      });

      it('tracking category calls packageTypeToTrackCategory', () => {
        createComponent({ packageEntity: conanPackage });
        expect(wrapper.vm.tracking.category).toBe(category);
        expect(utilSpy).toHaveBeenCalledWith('conan');
      });

      it(`delete button on delete modal call event with ${TrackingActions.DELETE_PACKAGE}`, () => {
        createComponent({ packageEntity: npmPackage });
        findDeleteModal().vm.$emit('primary');
        expect(eventSpy).toHaveBeenCalledWith(
          category,
          TrackingActions.DELETE_PACKAGE,
          expect.any(Object),
        );
      });

      it(`canceling a package deletion tracks  ${TrackingActions.CANCEL_DELETE_PACKAGE}`, () => {
        createComponent({ packageEntity: npmPackage });

        findDeleteModal().vm.$emit('canceled');

        expect(eventSpy).toHaveBeenCalledWith(
          category,
          TrackingActions.CANCEL_DELETE_PACKAGE,
          expect.any(Object),
        );
      });

      it(`request a file deletion tracks  ${TrackingActions.REQUEST_DELETE_PACKAGE_FILE}`, () => {
        createComponent({ packageEntity: npmPackage });

        findPackageFiles().vm.$emit('delete-file', mavenFiles[0]);

        expect(eventSpy).toHaveBeenCalledWith(
          category,
          TrackingActions.REQUEST_DELETE_PACKAGE_FILE,
          expect.any(Object),
        );
      });

      it(`confirming a file deletion tracks  ${TrackingActions.DELETE_PACKAGE_FILE}`, () => {
        createComponent({ packageEntity: npmPackage });

        findPackageFiles().vm.$emit('delete-file', npmPackage);
        findDeleteFileModal().vm.$emit('primary');

        expect(eventSpy).toHaveBeenCalledWith(
          category,
          TrackingActions.REQUEST_DELETE_PACKAGE_FILE,
          expect.any(Object),
        );
      });

      it(`canceling a file deletion tracks  ${TrackingActions.CANCEL_DELETE_PACKAGE_FILE}`, () => {
        createComponent({ packageEntity: npmPackage });

        findPackageFiles().vm.$emit('delete-file', npmPackage);
        findDeleteFileModal().vm.$emit('canceled');

        expect(eventSpy).toHaveBeenCalledWith(
          category,
          TrackingActions.CANCEL_DELETE_PACKAGE_FILE,
          expect.any(Object),
        );
      });

      it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => {
        createComponent({ packageEntity: conanPackage });

        findPackageFiles().vm.$emit('download-file');
        expect(eventSpy).toHaveBeenCalledWith(
          category,
          TrackingActions.PULL_PACKAGE,
          expect.any(Object),
        );
      });
    });
  });
});
