/**
 * @license
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import '../../../test/common-test-setup-karma.js';
import './gr-edit-controls.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {createIronOverlayBackdropStyleEl} from '../../../test/test-utils.js';

const basicFixture = fixtureFromElement('gr-edit-controls');

suite('gr-edit-controls tests', () => {
  let element;

  let showDialogSpy;
  let closeDialogSpy;
  let queryStub;
  let ironOverlayBackdropStyleEl;

  setup(() => {
    ironOverlayBackdropStyleEl = createIronOverlayBackdropStyleEl();
    element = basicFixture.instantiate();
    element.change = {_number: '42'};
    showDialogSpy = sinon.spy(element, '_showDialog');
    closeDialogSpy = sinon.spy(element, '_closeDialog');
    sinon.stub(element, '_hideAllDialogs');
    queryStub = sinon.stub(element.$.restAPI, 'queryChangeFiles')
        .returns(Promise.resolve([]));
    flush();
  });

  teardown(() => {
    ironOverlayBackdropStyleEl.remove();
  });

  test('all actions exist', () => {
    // We take 1 away from the total found, due to an extra button being
    // added for the file uploads (browse).
    assert.equal(
        element.root.querySelectorAll('gr-button').length - 1,
        element._actions.length);
  });

  suite('edit button CUJ', () => {
    let navStubs;
    let openAutoCcmplete;

    setup(() => {
      navStubs = [
        sinon.stub(GerritNav, 'getEditUrlForDiff'),
        sinon.stub(GerritNav, 'navigateToRelativeUrl'),
      ];
      openAutoCcmplete = element.$.openDialog.querySelector('gr-autocomplete');
    });

    test('_isValidPath', () => {
      assert.isFalse(element._isValidPath(''));
      assert.isFalse(element._isValidPath('test/'));
      assert.isFalse(element._isValidPath('/'));
      assert.isTrue(element._isValidPath('test/path.cpp'));
      assert.isTrue(element._isValidPath('test.js'));
    });

    test('open', () => {
      MockInteractions.tap(element.shadowRoot.querySelector('#open'));
      element.patchNum = 1;
      return showDialogSpy.lastCall.returnValue.then(() => {
        assert.isTrue(element._hideAllDialogs.called);
        assert.isTrue(element.$.openDialog.disabled);
        assert.isFalse(queryStub.called);
        // Setup _focused manually - in headless mode Chrome sometimes don't
        // setup focus. flush and/or flushAsynchronousOperations don't help
        openAutoCcmplete._focused = true;
        openAutoCcmplete.noDebounce = true;
        openAutoCcmplete.text = 'src/test.cpp';
        assert.isTrue(queryStub.called);
        assert.isFalse(element.$.openDialog.disabled);
        MockInteractions.tap(element.$.openDialog.shadowRoot
            .querySelector('gr-button[primary]'));
        for (const stub of navStubs) { assert.isTrue(stub.called); }
        assert.deepEqual(GerritNav.getEditUrlForDiff.lastCall.args,
            [element.change, 'src/test.cpp', element.patchNum]);
        assert.isTrue(closeDialogSpy.called);
      });
    });

    test('cancel', () => {
      MockInteractions.tap(element.shadowRoot.querySelector('#open'));
      return showDialogSpy.lastCall.returnValue.then(() => {
        assert.isTrue(element.$.openDialog.disabled);
        openAutoCcmplete.noDebounce = true;
        openAutoCcmplete.text = 'src/test.cpp';
        assert.isFalse(element.$.openDialog.disabled);
        MockInteractions.tap(element.$.openDialog.shadowRoot
            .querySelector('gr-button'));
        for (const stub of navStubs) { assert.isFalse(stub.called); }
        assert.isTrue(closeDialogSpy.called);
        assert.equal(element._path, 'src/test.cpp');
      });
    });
  });

  suite('delete button CUJ', () => {
    let navStub;
    let deleteStub;
    let deleteAutocomplete;

    setup(() => {
      navStub = sinon.stub(GerritNav, 'navigateToChange');
      deleteStub = sinon.stub(element.$.restAPI, 'deleteFileInChangeEdit');
      deleteAutocomplete =
          element.$.deleteDialog.querySelector('gr-autocomplete');
    });

    test('delete', () => {
      deleteStub.returns(Promise.resolve({ok: true}));
      MockInteractions.tap(element.shadowRoot.querySelector('#delete'));
      return showDialogSpy.lastCall.returnValue.then(() => {
        assert.isTrue(element.$.deleteDialog.disabled);
        assert.isFalse(queryStub.called);
        // Setup _focused manually - in headless mode Chrome sometimes don't
        // setup focus. flush and/or flushAsynchronousOperations don't help
        deleteAutocomplete._focused = true;
        deleteAutocomplete.noDebounce = true;
        deleteAutocomplete.text = 'src/test.cpp';
        assert.isTrue(queryStub.called);
        assert.isFalse(element.$.deleteDialog.disabled);
        MockInteractions.tap(element.$.deleteDialog.shadowRoot
            .querySelector('gr-button[primary]'));
        flush();

        assert.isTrue(deleteStub.called);

        return deleteStub.lastCall.returnValue.then(() => {
          assert.equal(element._path, '');
          assert.isTrue(navStub.called);
          assert.isTrue(closeDialogSpy.called);
        });
      });
    });

    test('delete fails', () => {
      deleteStub.returns(Promise.resolve({ok: false}));
      MockInteractions.tap(element.shadowRoot.querySelector('#delete'));
      return showDialogSpy.lastCall.returnValue.then(() => {
        assert.isTrue(element.$.deleteDialog.disabled);
        assert.isFalse(queryStub.called);
        // Setup _focused manually - in headless mode Chrome sometimes don't
        // setup focus. flush and/or flushAsynchronousOperations don't help
        deleteAutocomplete._focused = true;
        deleteAutocomplete.noDebounce = true;
        deleteAutocomplete.text = 'src/test.cpp';
        assert.isTrue(queryStub.called);
        assert.isFalse(element.$.deleteDialog.disabled);
        MockInteractions.tap(element.$.deleteDialog.shadowRoot
            .querySelector('gr-button[primary]'));
        flush();

        assert.isTrue(deleteStub.called);

        return deleteStub.lastCall.returnValue.then(() => {
          assert.isFalse(navStub.called);
          assert.isFalse(closeDialogSpy.called);
        });
      });
    });

    test('cancel', () => {
      MockInteractions.tap(element.shadowRoot.querySelector('#delete'));
      return showDialogSpy.lastCall.returnValue.then(() => {
        assert.isTrue(element.$.deleteDialog.disabled);
        element.$.deleteDialog.querySelector('gr-autocomplete').text =
            'src/test.cpp';
        assert.isFalse(element.$.deleteDialog.disabled);
        MockInteractions.tap(element.$.deleteDialog.shadowRoot
            .querySelector('gr-button'));
        assert.isFalse(navStub.called);
        assert.isTrue(closeDialogSpy.called);
        assert.equal(element._path, 'src/test.cpp');
      });
    });
  });

  suite('rename button CUJ', () => {
    let navStub;
    let renameStub;
    let renameAutocomplete;
    const inputSelector = PolymerElement ?
      '.newPathIronInput' :
      '.newPathInput';

    setup(() => {
      navStub = sinon.stub(GerritNav, 'navigateToChange');
      renameStub = sinon.stub(element.$.restAPI, 'renameFileInChangeEdit');
      renameAutocomplete =
          element.$.renameDialog.querySelector('gr-autocomplete');
    });

    test('rename', () => {
      renameStub.returns(Promise.resolve({ok: true}));
      MockInteractions.tap(element.shadowRoot.querySelector('#rename'));
      return showDialogSpy.lastCall.returnValue.then(() => {
        assert.isTrue(element.$.renameDialog.disabled);
        assert.isFalse(queryStub.called);
        // Setup _focused manually - in headless mode Chrome sometimes don't
        // setup focus. flush and/or flushAsynchronousOperations don't help
        renameAutocomplete._focused = true;
        renameAutocomplete.noDebounce = true;
        renameAutocomplete.text = 'src/test.cpp';
        assert.isTrue(queryStub.called);
        assert.isTrue(element.$.renameDialog.disabled);

        element.$.renameDialog.querySelector(inputSelector).bindValue =
            'src/test.newPath';

        assert.isFalse(element.$.renameDialog.disabled);
        MockInteractions.tap(element.$.renameDialog.shadowRoot
            .querySelector('gr-button[primary]'));
        flush();

        assert.isTrue(renameStub.called);

        return renameStub.lastCall.returnValue.then(() => {
          assert.equal(element._path, '');
          assert.isTrue(navStub.called);
          assert.isTrue(closeDialogSpy.called);
        });
      });
    });

    test('rename fails', () => {
      renameStub.returns(Promise.resolve({ok: false}));
      MockInteractions.tap(element.shadowRoot.querySelector('#rename'));
      return showDialogSpy.lastCall.returnValue.then(() => {
        assert.isTrue(element.$.renameDialog.disabled);
        assert.isFalse(queryStub.called);
        // Setup _focused manually - in headless mode Chrome sometimes don't
        // setup focus. flush and/or flushAsynchronousOperations don't help
        renameAutocomplete._focused = true;
        renameAutocomplete.noDebounce = true;
        renameAutocomplete.text = 'src/test.cpp';
        assert.isTrue(queryStub.called);
        assert.isTrue(element.$.renameDialog.disabled);

        element.$.renameDialog.querySelector(inputSelector).bindValue =
            'src/test.newPath';

        assert.isFalse(element.$.renameDialog.disabled);
        MockInteractions.tap(element.$.renameDialog.shadowRoot
            .querySelector('gr-button[primary]'));
        flush();

        assert.isTrue(renameStub.called);

        return renameStub.lastCall.returnValue.then(() => {
          assert.isFalse(navStub.called);
          assert.isFalse(closeDialogSpy.called);
        });
      });
    });

    test('cancel', () => {
      MockInteractions.tap(element.shadowRoot.querySelector('#rename'));
      return showDialogSpy.lastCall.returnValue.then(() => {
        assert.isTrue(element.$.renameDialog.disabled);
        element.$.renameDialog.querySelector('gr-autocomplete').text =
            'src/test.cpp';
        element.$.renameDialog.querySelector(inputSelector).bindValue =
            'src/test.newPath';
        assert.isFalse(element.$.renameDialog.disabled);
        MockInteractions.tap(element.$.renameDialog.shadowRoot
            .querySelector('gr-button'));
        assert.isFalse(navStub.called);
        assert.isTrue(closeDialogSpy.called);
        assert.equal(element._path, 'src/test.cpp');
        assert.equal(element._newPath, 'src/test.newPath');
      });
    });
  });

  suite('restore button CUJ', () => {
    let navStub;
    let restoreStub;

    setup(() => {
      navStub = sinon.stub(GerritNav, 'navigateToChange');
      restoreStub = sinon.stub(element.$.restAPI, 'restoreFileInChangeEdit');
    });

    test('restore hidden by default', () => {
      assert.isTrue(element.shadowRoot
          .querySelector('#restore').classList.contains('invisible'));
    });

    test('restore', () => {
      restoreStub.returns(Promise.resolve({ok: true}));
      element._path = 'src/test.cpp';
      MockInteractions.tap(element.shadowRoot.querySelector('#restore'));
      return showDialogSpy.lastCall.returnValue.then(() => {
        MockInteractions.tap(element.$.restoreDialog.shadowRoot
            .querySelector('gr-button[primary]'));
        flush();

        assert.isTrue(restoreStub.called);
        assert.equal(restoreStub.lastCall.args[1], 'src/test.cpp');
        return restoreStub.lastCall.returnValue.then(() => {
          assert.equal(element._path, '');
          assert.isTrue(navStub.called);
          assert.isTrue(closeDialogSpy.called);
        });
      });
    });

    test('restore fails', () => {
      restoreStub.returns(Promise.resolve({ok: false}));
      element._path = 'src/test.cpp';
      MockInteractions.tap(element.shadowRoot.querySelector('#restore'));
      return showDialogSpy.lastCall.returnValue.then(() => {
        MockInteractions.tap(element.$.restoreDialog.shadowRoot
            .querySelector('gr-button[primary]'));
        flush();

        assert.isTrue(restoreStub.called);
        assert.equal(restoreStub.lastCall.args[1], 'src/test.cpp');
        return restoreStub.lastCall.returnValue.then(() => {
          assert.isFalse(navStub.called);
          assert.isFalse(closeDialogSpy.called);
        });
      });
    });

    test('cancel', () => {
      element._path = 'src/test.cpp';
      MockInteractions.tap(element.shadowRoot.querySelector('#restore'));
      return showDialogSpy.lastCall.returnValue.then(() => {
        MockInteractions.tap(element.$.restoreDialog.shadowRoot
            .querySelector('gr-button'));
        assert.isFalse(navStub.called);
        assert.isTrue(closeDialogSpy.called);
        assert.equal(element._path, 'src/test.cpp');
      });
    });
  });

  suite('save file upload', () => {
    let navStub;
    let fileStub;

    setup(() => {
      navStub = sinon.stub(GerritNav, 'navigateToChange');
      fileStub = sinon.stub(element.$.restAPI, 'saveFileUploadChangeEdit');
    });

    test('_handleUploadConfirm', () => {
      fileStub.returns(Promise.resolve({ok: true}));

      element.change = {
        _number: '1',
        project: 'project',
        revisions: {
          abcd: {_number: 1},
          efgh: {_number: 2},
        },
        current_revision: 'efgh',
      };

      element._handleUploadConfirm('test.php', 'base64').then(() => {
        assert.equal(
            navStub.lastCall.args,
            '/c/project/+/1');
      });
    });
  });

  test('openOpenDialog', done => {
    element.openOpenDialog('test/path.cpp')
        .then(() => {
          assert.isFalse(element.$.openDialog.hasAttribute('hidden'));
          assert.equal(
              element.$.openDialog.querySelector('gr-autocomplete').text,
              'test/path.cpp');
          done();
        });
  });

  test('_getDialogFromEvent', () => {
    const spy = sinon.spy(element, '_getDialogFromEvent');
    element.addEventListener('tap', element._getDialogFromEvent);

    MockInteractions.tap(element.$.openDialog);
    flush();
    assert.equal(spy.lastCall.returnValue.id, 'openDialog');

    MockInteractions.tap(element.$.deleteDialog);
    flush();
    assert.equal(spy.lastCall.returnValue.id, 'deleteDialog');

    MockInteractions.tap(
        element.$.deleteDialog.querySelector('gr-autocomplete'));
    flush();
    assert.equal(spy.lastCall.returnValue.id, 'deleteDialog');

    MockInteractions.tap(element);
    flush();
    assert.notOk(spy.lastCall.returnValue);
  });
});

