/**
 * @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 {GrReviewerUpdatesParser} from './gr-reviewer-updates-parser.js';
import {parseDate} from '../../../utils/date-util.js';

suite('gr-reviewer-updates-parser tests', () => {
  let instance;

  test('ignores changes without messages', () => {
    const change = {};
    sinon.stub(
        GrReviewerUpdatesParser.prototype, '_filterRemovedMessages');
    sinon.stub(
        GrReviewerUpdatesParser.prototype, '_groupUpdates');
    sinon.stub(
        GrReviewerUpdatesParser.prototype, '_formatUpdates');
    assert.strictEqual(GrReviewerUpdatesParser.parse(change), change);
    assert.isFalse(
        GrReviewerUpdatesParser.prototype._filterRemovedMessages.called);
    assert.isFalse(
        GrReviewerUpdatesParser.prototype._groupUpdates.called);
    assert.isFalse(
        GrReviewerUpdatesParser.prototype._formatUpdates.called);
  });

  test('ignores changes without reviewer updates', () => {
    const change = {
      messages: [],
    };
    sinon.stub(
        GrReviewerUpdatesParser.prototype, '_filterRemovedMessages');
    sinon.stub(
        GrReviewerUpdatesParser.prototype, '_groupUpdates');
    sinon.stub(
        GrReviewerUpdatesParser.prototype, '_formatUpdates');
    assert.strictEqual(GrReviewerUpdatesParser.parse(change), change);
    assert.isFalse(
        GrReviewerUpdatesParser.prototype._filterRemovedMessages.called);
    assert.isFalse(
        GrReviewerUpdatesParser.prototype._groupUpdates.called);
    assert.isFalse(
        GrReviewerUpdatesParser.prototype._formatUpdates.called);
  });

  test('ignores changes with empty reviewer updates', () => {
    const change = {
      messages: [],
      reviewer_updates: [],
    };
    sinon.stub(
        GrReviewerUpdatesParser.prototype, '_filterRemovedMessages');
    sinon.stub(
        GrReviewerUpdatesParser.prototype, '_groupUpdates');
    sinon.stub(
        GrReviewerUpdatesParser.prototype, '_formatUpdates');
    assert.strictEqual(GrReviewerUpdatesParser.parse(change), change);
    assert.isFalse(
        GrReviewerUpdatesParser.prototype._filterRemovedMessages.called);
    assert.isFalse(
        GrReviewerUpdatesParser.prototype._groupUpdates.called);
    assert.isFalse(
        GrReviewerUpdatesParser.prototype._formatUpdates.called);
  });

  test('filter removed messages', () => {
    const change = {
      messages: [
        {
          message: 'msg1',
          tag: 'autogenerated:gerrit:deleteReviewer',
        },
        {
          message: 'msg2',
          tag: 'foo',
        },
      ],
    };
    instance = new GrReviewerUpdatesParser(change);
    instance._filterRemovedMessages();
    assert.deepEqual(instance.result, {
      messages: [{
        message: 'msg2',
        tag: 'foo',
      }],
    });
  });

  test('group reviewer updates', () => {
    const reviewer1 = {_account_id: 1};
    const reviewer2 = {_account_id: 2};
    const date1 = '2017-01-26 12:11:50.000000000';
    const date2 = '2017-01-26 12:11:55.000000000'; // Within threshold.
    const date3 = '2017-01-26 12:33:50.000000000';
    const date4 = '2017-01-26 12:44:50.000000000';
    const makeItem = function(state, reviewer, opt_date, opt_author) {
      return {
        reviewer,
        updated: opt_date || date1,
        updated_by: opt_author || reviewer1,
        state,
      };
    };
    let change = {
      reviewer_updates: [
        makeItem('REVIEWER', reviewer1), // New group.
        makeItem('CC', reviewer2), // Appended.
        makeItem('REVIEWER', reviewer2, date2), // Overrides previous one.

        makeItem('CC', reviewer1, date2, reviewer2), // New group.

        makeItem('REMOVED', reviewer2, date3), // Group has no state change.
        makeItem('REVIEWER', reviewer2, date3),

        makeItem('CC', reviewer1, date4), // No change, removed.
        makeItem('REVIEWER', reviewer1, date4), // Forms new group
        makeItem('REMOVED', reviewer2, date4), // Should be grouped.
      ],
    };

    instance = new GrReviewerUpdatesParser(change);
    instance._groupUpdates();
    change = instance.result;

    assert.equal(change.reviewer_updates.length, 3);
    assert.equal(change.reviewer_updates[0].updates.length, 2);
    assert.equal(change.reviewer_updates[1].updates.length, 1);
    assert.equal(change.reviewer_updates[2].updates.length, 2);

    assert.equal(change.reviewer_updates[0].date, date1);
    assert.deepEqual(change.reviewer_updates[0].author, reviewer1);
    assert.deepEqual(change.reviewer_updates[0].updates, [
      {
        reviewer: reviewer1,
        state: 'REVIEWER',
      },
      {
        reviewer: reviewer2,
        state: 'REVIEWER',
      },
    ]);

    assert.equal(change.reviewer_updates[1].date, date2);
    assert.deepEqual(change.reviewer_updates[1].author, reviewer2);
    assert.deepEqual(change.reviewer_updates[1].updates, [
      {
        reviewer: reviewer1,
        state: 'CC',
        prev_state: 'REVIEWER',
      },
    ]);

    assert.equal(change.reviewer_updates[2].date, date4);
    assert.deepEqual(change.reviewer_updates[2].author, reviewer1);
    assert.deepEqual(change.reviewer_updates[2].updates, [
      {
        reviewer: reviewer1,
        prev_state: 'CC',
        state: 'REVIEWER',
      },
      {
        reviewer: reviewer2,
        prev_state: 'REVIEWER',
        state: 'REMOVED',
      },
    ]);
  });

  test('format reviewer updates', () => {
    const reviewer1 = {_account_id: 1};
    const reviewer2 = {_account_id: 2};
    const makeItem = function(prev, state, opt_reviewer) {
      return {
        reviewer: opt_reviewer || reviewer1,
        prev_state: prev,
        state,
      };
    };
    const makeUpdate = function(items) {
      return {
        author: reviewer1,
        updated: '',
        updates: items,
      };
    };
    const change = {
      reviewer_updates: [
        makeUpdate([
          makeItem(undefined, 'CC'),
          makeItem(undefined, 'CC', reviewer2),
        ]),
        makeUpdate([
          makeItem('CC', 'REVIEWER'),
          makeItem('REVIEWER', 'REMOVED'),
          makeItem('REMOVED', 'REVIEWER'),
          makeItem(undefined, 'REVIEWER', reviewer2),
        ]),
      ],
    };

    instance = new GrReviewerUpdatesParser(change);
    instance._formatUpdates();

    assert.equal(change.reviewer_updates.length, 2);
    assert.equal(change.reviewer_updates[0].updates.length, 1);
    assert.equal(change.reviewer_updates[1].updates.length, 3);

    let items = change.reviewer_updates[0].updates;
    assert.equal(items[0].message, 'Added to cc: ');
    assert.deepEqual(items[0].reviewers, [reviewer1, reviewer2]);

    items = change.reviewer_updates[1].updates;
    assert.equal(items[0].message, 'Moved from cc to reviewer: ');
    assert.deepEqual(items[0].reviewers, [reviewer1]);
    assert.equal(items[1].message, 'Removed from reviewer: ');
    assert.deepEqual(items[1].reviewers, [reviewer1]);
    assert.equal(items[2].message, 'Added to reviewer: ');
    assert.deepEqual(items[2].reviewers, [reviewer1, reviewer2]);
  });

  test('_advanceUpdates', () => {
    const T0 = parseDate('2017-02-17 19:04:18.000000000').getTime();
    const tplus = delta => new Date(T0 + delta)
        .toISOString()
        .replace('T', ' ')
        .replace('Z', '000000');
    const change = {
      reviewer_updates: [{
        date: tplus(0),
        type: 'REVIEWER_UPDATE',
        updates: [{
          message: 'same time update',
        }],
      }, {
        date: tplus(200),
        type: 'REVIEWER_UPDATE',
        updates: [{
          message: 'update within threshold',
        }],
      }, {
        date: tplus(600),
        type: 'REVIEWER_UPDATE',
        updates: [{
          message: 'update between messages',
        }],
      }, {
        date: tplus(1000),
        type: 'REVIEWER_UPDATE',
        updates: [{
          message: 'late update',
        }],
      }],
      messages: [{
        id: '6734489eb9d642de28dbf2bcf9bda875923800d8',
        date: tplus(0),
        message: 'Uploaded patch set 1.',
      }, {
        id: '6734489eb9d642de28dbf2bcf9bda875923800d8',
        date: tplus(800),
        message: 'Uploaded patch set 2.',
      }],
    };
    instance = new GrReviewerUpdatesParser(change);
    instance._advanceUpdates();
    const updates = instance.result.reviewer_updates;
    assert.isBelow(parseDate(updates[0].date).getTime(), T0);
    assert.isBelow(parseDate(updates[1].date).getTime(), T0);
    assert.equal(updates[2].date, tplus(100));
    assert.equal(updates[3].date, tplus(500));
  });
});

