import Vue from 'vue';
import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import notify from '~/lib/utils/notify';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './mock_data';
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';

const returnPromise = data =>
  new Promise(resolve => {
    resolve({
      data,
    });
  });

describe('mrWidgetOptions', () => {
  let vm;
  let MrWidgetOptions;

  const COLLABORATION_MESSAGE = 'Allows commits from members who can merge to the target branch';

  beforeEach(() => {
    // Prevent component mounting
    delete mrWidgetOptions.el;

    MrWidgetOptions = Vue.extend(mrWidgetOptions);
    vm = mountComponent(MrWidgetOptions, {
      mrData: { ...mockData },
    });
  });

  afterEach(() => {
    vm.$destroy();
  });

  describe('data', () => {
    it('should instantiate Store and Service', () => {
      expect(vm.mr).toBeDefined();
      expect(vm.service).toBeDefined();
    });
  });

  describe('computed', () => {
    describe('componentName', () => {
      it('should return merged component', () => {
        expect(vm.componentName).toEqual('mr-widget-merged');
      });

      it('should return conflicts component', () => {
        vm.mr.state = 'conflicts';

        expect(vm.componentName).toEqual('mr-widget-conflicts');
      });
    });

    describe('shouldRenderMergeHelp', () => {
      it('should return false for the initial merged state', () => {
        expect(vm.shouldRenderMergeHelp).toBeFalsy();
      });

      it('should return true for a state which requires help widget', () => {
        vm.mr.state = 'conflicts';

        expect(vm.shouldRenderMergeHelp).toBeTruthy();
      });
    });

    describe('shouldRenderPipelines', () => {
      it('should return true when hasCI is true', () => {
        vm.mr.hasCI = true;

        expect(vm.shouldRenderPipelines).toBeTruthy();
      });

      it('should return false when hasCI is false', () => {
        vm.mr.hasCI = false;

        expect(vm.shouldRenderPipelines).toBeFalsy();
      });
    });

    describe('shouldRenderRelatedLinks', () => {
      it('should return false for the initial data', () => {
        expect(vm.shouldRenderRelatedLinks).toBeFalsy();
      });

      it('should return true if there is relatedLinks in MR', () => {
        Vue.set(vm.mr, 'relatedLinks', {});

        expect(vm.shouldRenderRelatedLinks).toBeTruthy();
      });
    });

    describe('shouldRenderSourceBranchRemovalStatus', () => {
      beforeEach(() => {
        vm.mr.state = 'readyToMerge';
      });

      it('should return true when cannot remove source branch and branch will be removed', () => {
        vm.mr.canRemoveSourceBranch = false;
        vm.mr.shouldRemoveSourceBranch = true;

        expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(true);
      });

      it('should return false when can remove source branch and branch will be removed', () => {
        vm.mr.canRemoveSourceBranch = true;
        vm.mr.shouldRemoveSourceBranch = true;

        expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
      });

      it('should return false when cannot remove source branch and branch will not be removed', () => {
        vm.mr.canRemoveSourceBranch = false;
        vm.mr.shouldRemoveSourceBranch = false;

        expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
      });

      it('should return false when in merged state', () => {
        vm.mr.canRemoveSourceBranch = false;
        vm.mr.shouldRemoveSourceBranch = true;
        vm.mr.state = 'merged';

        expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
      });

      it('should return false when in nothing to merge state', () => {
        vm.mr.canRemoveSourceBranch = false;
        vm.mr.shouldRemoveSourceBranch = true;
        vm.mr.state = 'nothingToMerge';

        expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
      });
    });

    describe('shouldRenderCollaborationStatus', () => {
      describe('when collaboration is allowed', () => {
        beforeEach(() => {
          vm.mr.allowCollaboration = true;
        });

        describe('when merge request is opened', () => {
          beforeEach(done => {
            vm.mr.isOpen = true;
            vm.$nextTick(done);
          });

          it('should render collaboration status', () => {
            expect(vm.$el.textContent).toContain(COLLABORATION_MESSAGE);
          });
        });

        describe('when merge request is not opened', () => {
          beforeEach(done => {
            vm.mr.isOpen = false;
            vm.$nextTick(done);
          });

          it('should not render collaboration status', () => {
            expect(vm.$el.textContent).not.toContain(COLLABORATION_MESSAGE);
          });
        });
      });

      describe('when collaboration is not allowed', () => {
        beforeEach(() => {
          vm.mr.allowCollaboration = false;
        });

        describe('when merge request is opened', () => {
          beforeEach(done => {
            vm.mr.isOpen = true;
            vm.$nextTick(done);
          });

          it('should not render collaboration status', () => {
            expect(vm.$el.textContent).not.toContain(COLLABORATION_MESSAGE);
          });
        });
      });
    });

    describe('showMergePipelineForkWarning', () => {
      describe('when the source project and target project are the same', () => {
        beforeEach(done => {
          Vue.set(vm.mr, 'mergePipelinesEnabled', true);
          Vue.set(vm.mr, 'sourceProjectId', 1);
          Vue.set(vm.mr, 'targetProjectId', 1);
          vm.$nextTick(done);
        });

        it('should be false', () => {
          expect(vm.showMergePipelineForkWarning).toEqual(false);
        });
      });

      describe('when merge pipelines are not enabled', () => {
        beforeEach(done => {
          Vue.set(vm.mr, 'mergePipelinesEnabled', false);
          Vue.set(vm.mr, 'sourceProjectId', 1);
          Vue.set(vm.mr, 'targetProjectId', 2);
          vm.$nextTick(done);
        });

        it('should be false', () => {
          expect(vm.showMergePipelineForkWarning).toEqual(false);
        });
      });

      describe('when merge pipelines are enabled _and_ the source project and target project are different', () => {
        beforeEach(done => {
          Vue.set(vm.mr, 'mergePipelinesEnabled', true);
          Vue.set(vm.mr, 'sourceProjectId', 1);
          Vue.set(vm.mr, 'targetProjectId', 2);
          vm.$nextTick(done);
        });

        it('should be true', () => {
          expect(vm.showMergePipelineForkWarning).toEqual(true);
        });
      });
    });
  });

  describe('methods', () => {
    describe('checkStatus', () => {
      it('should tell service to check status', done => {
        spyOn(vm.service, 'checkStatus').and.returnValue(returnPromise(mockData));
        spyOn(vm.mr, 'setData');
        spyOn(vm, 'handleNotification');

        let isCbExecuted = false;
        const cb = () => {
          isCbExecuted = true;
        };

        vm.checkStatus(cb);

        setTimeout(() => {
          expect(vm.service.checkStatus).toHaveBeenCalled();
          expect(vm.mr.setData).toHaveBeenCalled();
          expect(vm.handleNotification).toHaveBeenCalledWith(mockData);
          expect(isCbExecuted).toBeTruthy();
          done();
        }, 333);
      });
    });

    describe('initPolling', () => {
      it('should call SmartInterval', () => {
        spyOn(vm, 'checkStatus').and.returnValue(Promise.resolve());
        jasmine.clock().install();
        vm.initPolling();

        expect(vm.checkStatus).not.toHaveBeenCalled();

        jasmine.clock().tick(10000);

        expect(vm.pollingInterval).toBeDefined();
        expect(vm.checkStatus).toHaveBeenCalled();

        jasmine.clock().uninstall();
      });
    });

    describe('initDeploymentsPolling', () => {
      it('should call SmartInterval', () => {
        spyOn(vm, 'fetchDeployments').and.returnValue(Promise.resolve());
        vm.initDeploymentsPolling();

        expect(vm.deploymentsInterval).toBeDefined();
        expect(vm.fetchDeployments).toHaveBeenCalled();
      });
    });

    describe('fetchDeployments', () => {
      it('should fetch deployments', done => {
        spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));

        vm.fetchPreMergeDeployments();

        setTimeout(() => {
          expect(vm.service.fetchDeployments).toHaveBeenCalled();
          expect(vm.mr.deployments.length).toEqual(1);
          expect(vm.mr.deployments[0].id).toBe(1);
          done();
        });
      });
    });

    describe('fetchActionsContent', () => {
      it('should fetch content of Cherry Pick and Revert modals', done => {
        spyOn(vm.service, 'fetchMergeActionsContent').and.returnValue(returnPromise('hello world'));

        vm.fetchActionsContent();

        setTimeout(() => {
          expect(vm.service.fetchMergeActionsContent).toHaveBeenCalled();
          expect(document.body.textContent).toContain('hello world');
          done();
        }, 333);
      });
    });

    describe('bindEventHubListeners', () => {
      it('should bind eventHub listeners', () => {
        spyOn(vm, 'checkStatus').and.returnValue(() => {});
        spyOn(vm.service, 'checkStatus').and.returnValue(returnPromise(mockData));
        spyOn(vm, 'fetchActionsContent');
        spyOn(vm.mr, 'setData');
        spyOn(vm, 'resumePolling');
        spyOn(vm, 'stopPolling');
        spyOn(eventHub, '$on');

        vm.bindEventHubListeners();

        eventHub.$emit('SetBranchRemoveFlag', ['flag']);

        expect(vm.mr.isRemovingSourceBranch).toEqual('flag');

        eventHub.$emit('FailedToMerge');

        expect(vm.mr.state).toEqual('failedToMerge');

        eventHub.$emit('UpdateWidgetData', mockData);

        expect(vm.mr.setData).toHaveBeenCalledWith(mockData);

        eventHub.$emit('EnablePolling');

        expect(vm.resumePolling).toHaveBeenCalled();

        eventHub.$emit('DisablePolling');

        expect(vm.stopPolling).toHaveBeenCalled();

        const listenersWithServiceRequest = {
          MRWidgetUpdateRequested: true,
          FetchActionsContent: true,
        };

        const allArgs = eventHub.$on.calls.allArgs();
        allArgs.forEach(params => {
          const eventName = params[0];
          const callback = params[1];

          if (listenersWithServiceRequest[eventName]) {
            listenersWithServiceRequest[eventName] = callback;
          }
        });

        listenersWithServiceRequest.MRWidgetUpdateRequested();

        expect(vm.checkStatus).toHaveBeenCalled();

        listenersWithServiceRequest.FetchActionsContent();

        expect(vm.fetchActionsContent).toHaveBeenCalled();
      });
    });

    describe('setFavicon', () => {
      let faviconElement;

      beforeEach(() => {
        const favicon = document.createElement('link');
        favicon.setAttribute('id', 'favicon');
        favicon.setAttribute('data-original-href', faviconDataUrl);
        document.body.appendChild(favicon);

        faviconElement = document.getElementById('favicon');
      });

      afterEach(() => {
        document.body.removeChild(document.getElementById('favicon'));
      });

      it('should call setFavicon method', done => {
        vm.mr.ciStatusFaviconPath = overlayDataUrl;
        vm.setFaviconHelper()
          .then(() => {
            /*
            It would be better if we'd could mock commonUtils.setFaviconURL
            with a spy and test that it was called. We are doing the following
            tests as a proxy to show that the function has been called
            */
            expect(faviconElement.getAttribute('href')).not.toEqual(null);
            expect(faviconElement.getAttribute('href')).not.toEqual(overlayDataUrl);
            expect(faviconElement.getAttribute('href')).not.toEqual(faviconDataUrl);
            done();
          })
          .catch(done.fail);
      });

      it('should not call setFavicon when there is no ciStatusFaviconPath', done => {
        vm.mr.ciStatusFaviconPath = null;
        vm.setFaviconHelper()
          .then(() => {
            expect(faviconElement.getAttribute('href')).toEqual(null);
            done();
          })
          .catch(done.fail);
      });
    });

    describe('handleNotification', () => {
      const data = {
        ci_status: 'running',
        title: 'title',
        pipeline: { details: { status: { label: 'running-label' } } },
      };

      beforeEach(() => {
        spyOn(notify, 'notifyMe');

        vm.mr.ciStatus = 'failed';
        vm.mr.gitlabLogo = 'logo.png';
      });

      it('should call notifyMe', () => {
        vm.handleNotification(data);

        expect(notify.notifyMe).toHaveBeenCalledWith(
          'Pipeline running-label',
          'Pipeline running-label for "title"',
          'logo.png',
        );
      });

      it('should not call notifyMe if the status has not changed', () => {
        vm.mr.ciStatus = data.ci_status;

        vm.handleNotification(data);

        expect(notify.notifyMe).not.toHaveBeenCalled();
      });

      it('should not notify if no pipeline provided', () => {
        vm.handleNotification({
          ...data,
          pipeline: undefined,
        });

        expect(notify.notifyMe).not.toHaveBeenCalled();
      });
    });

    describe('resumePolling', () => {
      it('should call stopTimer on pollingInterval', () => {
        spyOn(vm.pollingInterval, 'resume');

        vm.resumePolling();

        expect(vm.pollingInterval.resume).toHaveBeenCalled();
      });
    });

    describe('stopPolling', () => {
      it('should call stopTimer on pollingInterval', () => {
        spyOn(vm.pollingInterval, 'stopTimer');

        vm.stopPolling();

        expect(vm.pollingInterval.stopTimer).toHaveBeenCalled();
      });
    });
  });

  describe('rendering relatedLinks', () => {
    beforeEach(done => {
      vm.mr.relatedLinks = {
        assignToMe: null,
        closing: `
          <a class="close-related-link" href="#">
            Close
          </a>
        `,
        mentioned: '',
      };
      Vue.nextTick(done);
    });

    it('renders if there are relatedLinks', () => {
      expect(vm.$el.querySelector('.close-related-link')).toBeDefined();
    });

    it('does not render if state is nothingToMerge', done => {
      vm.mr.state = stateKey.nothingToMerge;
      Vue.nextTick(() => {
        expect(vm.$el.querySelector('.close-related-link')).toBeNull();
        done();
      });
    });
  });

  describe('rendering source branch removal status', () => {
    it('renders when user cannot remove branch and branch should be removed', done => {
      vm.mr.canRemoveSourceBranch = false;
      vm.mr.shouldRemoveSourceBranch = true;
      vm.mr.state = 'readyToMerge';

      vm.$nextTick(() => {
        const tooltip = vm.$el.querySelector('.fa-question-circle');

        expect(vm.$el.textContent).toContain('Deletes source branch');
        expect(tooltip.getAttribute('data-original-title')).toBe(
          'A user with write access to the source branch selected this option',
        );

        done();
      });
    });

    it('does not render in merged state', done => {
      vm.mr.canRemoveSourceBranch = false;
      vm.mr.shouldRemoveSourceBranch = true;
      vm.mr.state = 'merged';

      vm.$nextTick(() => {
        expect(vm.$el.textContent).toContain('The source branch has been deleted');
        expect(vm.$el.textContent).not.toContain('Deletes source branch');

        done();
      });
    });
  });

  describe('rendering deployments', () => {
    const changes = [
      {
        path: 'index.html',
        external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
      },
      {
        path: 'imgs/gallery.html',
        external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
      },
      {
        path: 'about/',
        external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
      },
    ];
    const deploymentMockData = {
      id: 15,
      name: 'review/diplo',
      url: '/root/acets-review-apps/environments/15',
      stop_url: '/root/acets-review-apps/environments/15/stop',
      metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
      metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
      external_url: 'http://diplo.',
      external_url_formatted: 'diplo.',
      deployed_at: '2017-03-22T22:44:42.258Z',
      deployed_at_formatted: 'Mar 22, 2017 10:44pm',
      changes,
      status: 'success',
    };

    beforeEach(done => {
      vm.mr.deployments.push(
        {
          ...deploymentMockData,
        },
        {
          ...deploymentMockData,
          id: deploymentMockData.id + 1,
        },
      );

      vm.$nextTick(done);
    });

    it('renders multiple deployments', () => {
      expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2);
    });

    it('renders dropdpown with multiple file changes', () => {
      expect(
        vm.$el
          .querySelector('.js-mr-wigdet-deployment-dropdown')
          .querySelectorAll('.js-filtered-dropdown-result').length,
      ).toEqual(changes.length);
    });
  });

  describe('pipeline for target branch after merge', () => {
    describe('with information for target branch pipeline', () => {
      beforeEach(done => {
        vm.mr.state = 'merged';
        vm.mr.mergePipeline = {
          id: 127,
          user: {
            id: 1,
            name: 'Administrator',
            username: 'root',
            state: 'active',
            avatar_url: null,
            web_url: 'http://localhost:3000/root',
            status_tooltip_html: null,
            path: '/root',
          },
          active: true,
          coverage: null,
          source: 'push',
          created_at: '2018-10-22T11:41:35.186Z',
          updated_at: '2018-10-22T11:41:35.433Z',
          path: '/root/ci-web-terminal/pipelines/127',
          flags: {
            latest: true,
            stuck: true,
            auto_devops: false,
            yaml_errors: false,
            retryable: false,
            cancelable: true,
            failure_reason: false,
          },
          details: {
            status: {
              icon: 'status_pending',
              text: 'pending',
              label: 'pending',
              group: 'pending',
              tooltip: 'pending',
              has_details: true,
              details_path: '/root/ci-web-terminal/pipelines/127',
              illustration: null,
              favicon:
                '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
            },
            duration: null,
            finished_at: null,
            stages: [
              {
                name: 'test',
                title: 'test: pending',
                status: {
                  icon: 'status_pending',
                  text: 'pending',
                  label: 'pending',
                  group: 'pending',
                  tooltip: 'pending',
                  has_details: true,
                  details_path: '/root/ci-web-terminal/pipelines/127#test',
                  illustration: null,
                  favicon:
                    '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
                },
                path: '/root/ci-web-terminal/pipelines/127#test',
                dropdown_path: '/root/ci-web-terminal/pipelines/127/stage.json?stage=test',
              },
            ],
            artifacts: [],
            manual_actions: [],
            scheduled_actions: [],
          },
          ref: {
            name: 'master',
            path: '/root/ci-web-terminal/commits/master',
            tag: false,
            branch: true,
          },
          commit: {
            id: 'aa1939133d373c94879becb79d91828a892ee319',
            short_id: 'aa193913',
            title: "Merge branch 'master-test' into 'master'",
            created_at: '2018-10-22T11:41:33.000Z',
            parent_ids: [
              '4622f4dd792468993003caf2e3be978798cbe096',
              '76598df914cdfe87132d0c3c40f80db9fa9396a4',
            ],
            message:
              "Merge branch 'master-test' into 'master'\n\nUpdate .gitlab-ci.yml\n\nSee merge request root/ci-web-terminal!1",
            author_name: 'Administrator',
            author_email: 'admin@example.com',
            authored_date: '2018-10-22T11:41:33.000Z',
            committer_name: 'Administrator',
            committer_email: 'admin@example.com',
            committed_date: '2018-10-22T11:41:33.000Z',
            author: {
              id: 1,
              name: 'Administrator',
              username: 'root',
              state: 'active',
              avatar_url: null,
              web_url: 'http://localhost:3000/root',
              status_tooltip_html: null,
              path: '/root',
            },
            author_gravatar_url: null,
            commit_url:
              'http://localhost:3000/root/ci-web-terminal/commit/aa1939133d373c94879becb79d91828a892ee319',
            commit_path: '/root/ci-web-terminal/commit/aa1939133d373c94879becb79d91828a892ee319',
          },
          cancel_path: '/root/ci-web-terminal/pipelines/127/cancel',
        };
        vm.$nextTick(done);
      });

      it('renders pipeline block', () => {
        expect(vm.$el.querySelector('.js-post-merge-pipeline')).not.toBeNull();
      });

      describe('with post merge deployments', () => {
        beforeEach(done => {
          vm.mr.postMergeDeployments = [
            {
              id: 15,
              name: 'review/diplo',
              url: '/root/acets-review-apps/environments/15',
              stop_url: '/root/acets-review-apps/environments/15/stop',
              metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
              metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
              external_url: 'http://diplo.',
              external_url_formatted: 'diplo.',
              deployed_at: '2017-03-22T22:44:42.258Z',
              deployed_at_formatted: 'Mar 22, 2017 10:44pm',
              changes: [
                {
                  path: 'index.html',
                  external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
                },
                {
                  path: 'imgs/gallery.html',
                  external_url:
                    'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
                },
                {
                  path: 'about/',
                  external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
                },
              ],
              status: 'success',
            },
          ];

          vm.$nextTick(done);
        });

        it('renders post deployment information', () => {
          expect(vm.$el.querySelector('.js-post-deployment')).not.toBeNull();
        });
      });
    });

    describe('without information for target branch pipeline', () => {
      beforeEach(done => {
        vm.mr.state = 'merged';

        vm.$nextTick(done);
      });

      it('does not render pipeline block', () => {
        expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull();
      });
    });

    describe('when state is not merged', () => {
      beforeEach(done => {
        vm.mr.state = 'archived';

        vm.$nextTick(done);
      });

      it('does not render pipeline block', () => {
        expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull();
      });

      it('does not render post deployment information', () => {
        expect(vm.$el.querySelector('.js-post-deployment')).toBeNull();
      });
    });
  });
});
