/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import '../../../test/common-test-setup';
import {fixture, assert} from '@open-wc/testing';
import {html} from 'lit';
import './gr-hovercard-account';
import {GrHovercardAccount} from './gr-hovercard-account';
import {
  mockPromise,
  query,
  queryAndAssert,
  stubRestApi,
} from '../../../test/test-utils';
import {
  AccountDetailInfo,
  AccountId,
  EmailAddress,
  ReviewerState,
} from '../../../api/rest-api';
import {
  createAccountDetailWithId,
  createChange,
  createDetailedLabelInfo,
} from '../../../test/test-data-generators';
import {GrButton} from '../gr-button/gr-button';
import {EventType} from '../../../types/events';

suite('gr-hovercard-account tests', () => {
  let element: GrHovercardAccount;

  const ACCOUNT: AccountDetailInfo = {
    ...createAccountDetailWithId(31),
    email: 'kermit@gmail.com' as EmailAddress,
    username: 'kermit',
    name: 'Kermit The Frog',
    status: 'I am a frog',
    _account_id: 31415926535 as AccountId,
  };

  setup(async () => {
    const change = {
      ...createChange(),
      attention_set: {},
      reviewers: {},
      owner: {...ACCOUNT},
    };
    element = await fixture<GrHovercardAccount>(
      html`<gr-hovercard-account
        class="hovered"
        .account=${ACCOUNT}
        .change=${change}
      >
      </gr-hovercard-account>`
    );
    await element.show({});
    element.userModel.setAccount({...ACCOUNT});
    await element.updateComplete;
  });

  teardown(async () => {
    element.mouseHide(new MouseEvent('click'));
    await element.updateComplete;
  });

  test('renders', () => {
    assert.shadowDom.equal(
      element,
      /* HTML */ `
        <div id="container" role="tooltip" tabindex="-1">
          <div class="top">
            <div class="avatar">
              <gr-avatar hidden="" imagesize="56"></gr-avatar>
            </div>
            <div class="account">
              <h3 class="heading-3 name">Kermit The Frog</h3>
              <div class="email">kermit@gmail.com</div>
            </div>
          </div>
          <gr-endpoint-decorator name="hovercard-status">
            <gr-endpoint-param name="account"></gr-endpoint-param>
          </gr-endpoint-decorator>
          <div class="status">
            <span class="title">About me:</span>
            <span class="value">I am a frog</span>
          </div>
          <div class="links">
            <gr-icon icon="link" class="linkIcon"></gr-icon>
            <a href="/q/owner:kermit%2540gmail.com">Changes</a>
            ·
            <a href="/dashboard/31415926535">Dashboard</a>
          </div>
        </div>
      `
    );
  });

  test('renders without change data', async () => {
    const elementWithoutChange = await fixture<GrHovercardAccount>(
      html`<gr-hovercard-account class="hovered" .account=${ACCOUNT}>
      </gr-hovercard-account>`
    );
    await elementWithoutChange.show({});
    assert.shadowDom.equal(
      elementWithoutChange,
      /* HTML */ `
        <div id="container" role="tooltip" tabindex="-1">
          <div class="top">
            <div class="avatar">
              <gr-avatar hidden="" imagesize="56"> </gr-avatar>
            </div>
            <div class="account">
              <h3 class="heading-3 name">Kermit The Frog</h3>
              <div class="email">kermit@gmail.com</div>
            </div>
          </div>
          <gr-endpoint-decorator name="hovercard-status">
            <gr-endpoint-param name="account"> </gr-endpoint-param>
          </gr-endpoint-decorator>
          <div class="status">
            <span class="title"> About me: </span>
            <span class="value"> I am a frog </span>
          </div>
          <div class="links">
            <gr-icon class="linkIcon" icon="link"> </gr-icon>
            <a href="/q/owner:kermit%2540gmail.com"> Changes </a>
            ·
            <a href="/dashboard/31415926535"> Dashboard </a>
          </div>
        </div>
      `
    );
    elementWithoutChange.mouseHide(new MouseEvent('click'));
    await elementWithoutChange.updateComplete;
  });

  test('account name is shown', () => {
    const name = queryAndAssert<HTMLHeadingElement>(element, '.name');
    assert.equal(name.innerText, 'Kermit The Frog');
  });

  test('computePronoun', async () => {
    element.account = createAccountDetailWithId(1);
    element.selfAccount = createAccountDetailWithId(1);
    await element.updateComplete;
    assert.equal(element.computePronoun(), 'Your');
    element.account = createAccountDetailWithId(2);
    await element.updateComplete;
    assert.equal(element.computePronoun(), 'Their');
  });

  test('account status is not shown if the property is not set', async () => {
    element.account = {...ACCOUNT, status: undefined};
    await element.updateComplete;
    assert.isUndefined(query(element, '.status'));
  });

  test('account status is displayed', () => {
    const status = queryAndAssert<HTMLSpanElement>(element, '.status .value');
    assert.equal(status.innerText, 'I am a frog');
  });

  test('voteable div is not shown if the property is not set', () => {
    assert.isUndefined(query(element, '.voteable'));
  });

  test('voteable div is displayed', async () => {
    element.change = {
      ...createChange(),
      labels: {
        Foo: {
          ...createDetailedLabelInfo(),
          all: [
            {
              _account_id: 7 as AccountId,
              permitted_voting_range: {max: 2, min: 0},
            },
          ],
        },
        Bar: {
          ...createDetailedLabelInfo(),
          all: [
            {
              ...createAccountDetailWithId(1),
              permitted_voting_range: {max: 1, min: 0},
            },
            {
              _account_id: 7 as AccountId,
              permitted_voting_range: {max: 1, min: 0},
            },
          ],
        },
        FooBar: {
          ...createDetailedLabelInfo(),
          all: [{_account_id: 7 as AccountId, value: 0}],
        },
      },
      permitted_labels: {
        Foo: ['-1', ' 0', '+1', '+2'],
        FooBar: ['-1', ' 0'],
      },
    };
    element.account = createAccountDetailWithId(1);

    await element.updateComplete;
    const voteableEl = queryAndAssert<HTMLSpanElement>(
      element,
      '.voteable .value'
    );
    assert.equal(voteableEl.innerText, 'Bar: +1');
  });

  test('remove reviewer', async () => {
    element.change = {
      ...createChange(),
      removable_reviewers: [ACCOUNT],
      reviewers: {
        [ReviewerState.REVIEWER]: [ACCOUNT],
      },
    };
    await element.updateComplete;
    stubRestApi('removeChangeReviewer').returns(
      Promise.resolve({...new Response(), ok: true})
    );
    const reloadListener = sinon.spy();
    element._target?.addEventListener('reload', reloadListener);
    const button = queryAndAssert<GrButton>(element, '.removeReviewerOrCC');
    assert.isOk(button);
    assert.equal(button.innerText, 'Remove Reviewer');
    button.click();
    await element.updateComplete;
    assert.isTrue(reloadListener.called);
  });

  test('move reviewer to cc', async () => {
    element.change = {
      ...createChange(),
      removable_reviewers: [ACCOUNT],
      reviewers: {
        [ReviewerState.REVIEWER]: [ACCOUNT],
      },
    };
    await element.updateComplete;
    const saveReviewStub = stubRestApi('saveChangeReview').returns(
      Promise.resolve({...new Response(), ok: true})
    );
    stubRestApi('removeChangeReviewer').returns(
      Promise.resolve({...new Response(), ok: true})
    );
    const reloadListener = sinon.spy();
    element._target?.addEventListener('reload', reloadListener);

    const button = queryAndAssert<GrButton>(element, '.changeReviewerOrCC');

    assert.isOk(button);
    assert.equal(button.innerText, 'Move Reviewer to CC');
    button.click();
    await element.updateComplete;
    assert.isTrue(saveReviewStub.called);
    assert.isTrue(reloadListener.called);
  });

  test('move reviewer to cc', async () => {
    element.change = {
      ...createChange(),
      removable_reviewers: [ACCOUNT],
      reviewers: {
        [ReviewerState.REVIEWER]: [],
      },
    };
    await element.updateComplete;
    const saveReviewStub = stubRestApi('saveChangeReview').returns(
      Promise.resolve({...new Response(), ok: true})
    );
    stubRestApi('removeChangeReviewer').returns(
      Promise.resolve({...new Response(), ok: true})
    );
    const reloadListener = sinon.spy();
    element._target?.addEventListener('reload', reloadListener);

    const button = queryAndAssert<GrButton>(element, '.changeReviewerOrCC');
    assert.isOk(button);
    assert.equal(button.innerText, 'Move CC to Reviewer');

    button.click();
    await element.updateComplete;
    assert.isTrue(saveReviewStub.called);
    assert.isTrue(reloadListener.called);
  });

  test('remove cc', async () => {
    element.change = {
      ...createChange(),
      removable_reviewers: [ACCOUNT],
      reviewers: {
        [ReviewerState.REVIEWER]: [],
      },
    };
    await element.updateComplete;
    stubRestApi('removeChangeReviewer').returns(
      Promise.resolve({...new Response(), ok: true})
    );
    const reloadListener = sinon.spy();
    element._target?.addEventListener('reload', reloadListener);

    const button = queryAndAssert<GrButton>(element, '.removeReviewerOrCC');

    assert.equal(button.innerText, 'Remove CC');
    assert.isOk(button);
    button.click();
    await element.updateComplete;
    assert.isTrue(reloadListener.called);
  });

  test('add to attention set', async () => {
    const apiPromise = mockPromise<Response>();
    const apiSpy = stubRestApi('addToAttentionSet').returns(apiPromise);
    element.highlightAttention = true;
    element._target = document.createElement('div');
    await element.updateComplete;
    const showAlertListener = sinon.spy();
    const hideAlertListener = sinon.spy();
    const updatedListener = sinon.spy();
    element._target.addEventListener(EventType.SHOW_ALERT, showAlertListener);
    element._target.addEventListener('hide-alert', hideAlertListener);
    element._target.addEventListener('attention-set-updated', updatedListener);

    const button = queryAndAssert<GrButton>(element, '.addToAttentionSet');
    assert.isOk(button);
    assert.isTrue(element._isShowing, 'hovercard is showing');
    button.click();

    assert.equal(Object.keys(element.change?.attention_set ?? {}).length, 1);
    const attention_set_info = Object.values(
      element.change?.attention_set ?? {}
    )[0];
    assert.equal(
      attention_set_info.reason,
      `Added by <GERRIT_ACCOUNT_${ACCOUNT._account_id}>` +
        ' using the hovercard menu'
    );
    assert.equal(
      attention_set_info.reason_account?._account_id,
      ACCOUNT._account_id
    );
    assert.isTrue(showAlertListener.called, 'showAlertListener was called');
    assert.isTrue(updatedListener.called, 'updatedListener was called');
    assert.isFalse(element._isShowing, 'hovercard is hidden');

    apiPromise.resolve({...new Response(), ok: true});
    await element.updateComplete;
    assert.isTrue(apiSpy.calledOnce);
    assert.equal(
      apiSpy.lastCall.args[2],
      `Added by <GERRIT_ACCOUNT_${ACCOUNT._account_id}>` +
        ' using the hovercard menu'
    );
    assert.isTrue(hideAlertListener.called, 'hideAlertListener was called');
  });

  test('remove from attention set', async () => {
    const apiPromise = mockPromise<Response>();
    const apiSpy = stubRestApi('removeFromAttentionSet').returns(apiPromise);
    element.highlightAttention = true;
    element.change = {
      ...createChange(),
      attention_set: {
        '31415926535': {account: ACCOUNT, reason: 'a good reason'},
      },
      reviewers: {},
      owner: {...ACCOUNT},
    };
    element._target = document.createElement('div');
    await element.updateComplete;
    const showAlertListener = sinon.spy();
    const hideAlertListener = sinon.spy();
    const updatedListener = sinon.spy();
    element._target.addEventListener(EventType.SHOW_ALERT, showAlertListener);
    element._target.addEventListener('hide-alert', hideAlertListener);
    element._target.addEventListener('attention-set-updated', updatedListener);

    const button = queryAndAssert<GrButton>(element, '.removeFromAttentionSet');
    assert.isOk(button);
    assert.isTrue(element._isShowing, 'hovercard is showing');
    button.click();

    assert.isDefined(element.change?.attention_set);
    assert.equal(Object.keys(element.change?.attention_set ?? {}).length, 0);
    assert.isTrue(showAlertListener.called, 'showAlertListener was called');
    assert.isTrue(updatedListener.called, 'updatedListener was called');
    assert.isFalse(element._isShowing, 'hovercard is hidden');

    apiPromise.resolve({...new Response(), ok: true});
    await element.updateComplete;

    assert.isTrue(apiSpy.calledOnce);
    assert.equal(
      apiSpy.lastCall.args[2],
      `Removed by <GERRIT_ACCOUNT_${ACCOUNT._account_id}>` +
        ' using the hovercard menu'
    );
    assert.isTrue(hideAlertListener.called, 'hideAlertListener was called');
  });
});
