/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import '../../../test/common-test-setup';
import './gr-group-members';
import {GrGroupMembers, ItemType} from './gr-group-members';
import {
  addListenerForTest,
  mockPromise,
  queryAll,
  queryAndAssert,
  stubBaseUrl,
  stubRestApi,
  waitUntil,
} from '../../../test/test-utils';
import {
  AccountId,
  AccountInfo,
  EmailAddress,
  GroupId,
  GroupInfo,
  GroupName,
} from '../../../types/common';
import {GrButton} from '../../shared/gr-button/gr-button';
import {GrAutocomplete} from '../../shared/gr-autocomplete/gr-autocomplete';
import {EventType, PageErrorEvent} from '../../../types/events';
import {getAccountSuggestions} from '../../../utils/account-util';
import {getAppContext} from '../../../services/app-context';
import {fixture, html, assert} from '@open-wc/testing';
import {createServerInfo} from '../../../test/test-data-generators';

suite('gr-group-members tests', () => {
  let element: GrGroupMembers;

  let groups: GroupInfo;
  let groupMembers: AccountInfo[];
  let includedGroups: GroupInfo[];
  let groupStub: sinon.SinonStub;

  setup(async () => {
    groups = {
      id: 'testId1' as GroupId,
      name: 'Administrators' as GroupName,
      owner: 'Administrators',
      group_id: 1,
    };

    groupMembers = [
      {
        _account_id: 1000097 as AccountId,
        name: 'Jane Roe',
        email: 'jane.roe@example.com' as EmailAddress,
        username: 'jane',
      },
      {
        _account_id: 1000096 as AccountId,
        name: 'Test User',
        email: 'john.doe@example.com' as EmailAddress,
      },
      {
        _account_id: 1000095 as AccountId,
        name: 'Gerrit',
      },
      {
        _account_id: 1000098 as AccountId,
      },
    ];

    includedGroups = [
      {
        url: 'https://group/url',
        options: {
          visible_to_all: false,
        },
        id: 'testId' as GroupId,
        name: 'testName' as GroupName,
      },
      {
        url: '/group/url',
        options: {
          visible_to_all: false,
        },
        id: 'testId2' as GroupId,
        name: 'testName2' as GroupName,
      },
      {
        url: '#/group/url',
        options: {
          visible_to_all: false,
        },
        id: 'testId3' as GroupId,
        name: 'testName3' as GroupName,
      },
    ];

    stubRestApi('getSuggestedAccounts').callsFake(input => {
      if (input.startsWith('test')) {
        return Promise.resolve([
          {
            _account_id: 1000096 as AccountId,
            name: 'test-account',
            email: 'test.account@example.com' as EmailAddress,
            username: 'test123',
            display_name: 'display-test-account',
          },
          {
            _account_id: 1001439 as AccountId,
            name: 'test-admin',
            email: 'test.admin@example.com' as EmailAddress,
            username: 'test_admin',
          },
          {
            _account_id: 1001439 as AccountId,
            name: 'test-git',
            username: 'test_git',
          },
        ]);
      } else {
        return Promise.resolve([]);
      }
    });
    stubRestApi('getSuggestedGroups').callsFake(input => {
      if (input.startsWith('test')) {
        return Promise.resolve({
          'test-admin': {
            id: '1ce023d3fb4e4260776fb92cd08b52bbd21ce70a',
          },
          'test/Administrator (admin)': {
            id: 'test%3Aadmin',
          },
        });
      } else {
        return Promise.resolve({});
      }
    });
    stubRestApi('getGroupMembers').returns(Promise.resolve(groupMembers));
    stubRestApi('getIsGroupOwner').returns(Promise.resolve(true));
    stubRestApi('getIncludedGroup').returns(Promise.resolve(includedGroups));
    element = await fixture(html`<gr-group-members></gr-group-members>`);
    stubBaseUrl('https://test/site');
    element.groupId = 'testId1' as GroupId;
    groupStub = stubRestApi('getGroupConfig').returns(Promise.resolve(groups));
    return element.loadGroupDetails();
  });

  test('render', () => {
    assert.shadowDom.equal(
      element,
      /* HTML */ `
        <div class="gr-form-styles main">
          <div id="loading">Loading...</div>
          <div id="loadedContent">
            <h1 class="heading-1" id="Title">Administrators</h1>
            <div id="form">
              <h3 class="heading-3" id="members">Members</h3>
              <fieldset>
                <span class="value">
                  <gr-autocomplete
                    id="groupMemberSearchInput"
                    placeholder="Name Or Email"
                  >
                  </gr-autocomplete>
                </span>
                <gr-button
                  aria-disabled="true"
                  disabled=""
                  id="saveGroupMember"
                  role="button"
                  tabindex="-1"
                >
                  Add
                </gr-button>
                <table id="groupMembers">
                  <tbody>
                    <tr class="headerRow">
                      <th class="nameHeader">Name</th>
                      <th class="emailAddressHeader">Email Address</th>
                      <th class="deleteHeader">Delete Member</th>
                    </tr>
                  </tbody>
                  <tbody>
                    <tr>
                      <td class="nameColumn">
                        <gr-account-label clickable="" deselected="">
                        </gr-account-label>
                      </td>
                      <td>jane.roe@example.com</td>
                      <td class="deleteColumn">
                        <gr-button
                          aria-disabled="false"
                          class="deleteMembersButton"
                          data-index="0"
                          role="button"
                          tabindex="0"
                        >
                          Delete
                        </gr-button>
                      </td>
                    </tr>
                    <tr>
                      <td class="nameColumn">
                        <gr-account-label clickable="" deselected="">
                        </gr-account-label>
                      </td>
                      <td>john.doe@example.com</td>
                      <td class="deleteColumn">
                        <gr-button
                          aria-disabled="false"
                          class="deleteMembersButton"
                          data-index="1"
                          role="button"
                          tabindex="0"
                        >
                          Delete
                        </gr-button>
                      </td>
                    </tr>
                    <tr>
                      <td class="nameColumn">
                        <gr-account-label clickable="" deselected="">
                        </gr-account-label>
                      </td>
                      <td></td>
                      <td class="deleteColumn">
                        <gr-button
                          aria-disabled="false"
                          class="deleteMembersButton"
                          data-index="2"
                          role="button"
                          tabindex="0"
                        >
                          Delete
                        </gr-button>
                      </td>
                    </tr>
                    <tr>
                      <td class="nameColumn">
                        <gr-account-label clickable="" deselected="">
                        </gr-account-label>
                      </td>
                      <td></td>
                      <td class="deleteColumn">
                        <gr-button
                          aria-disabled="false"
                          class="deleteMembersButton"
                          data-index="3"
                          role="button"
                          tabindex="0"
                        >
                          Delete
                        </gr-button>
                      </td>
                    </tr>
                  </tbody>
                </table>
              </fieldset>
              <h3 class="heading-3" id="includedGroups">Included Groups</h3>
              <fieldset>
                <span class="value">
                  <gr-autocomplete
                    id="includedGroupSearchInput"
                    placeholder="Group Name"
                  >
                  </gr-autocomplete>
                </span>
                <gr-button
                  aria-disabled="true"
                  disabled=""
                  id="saveIncludedGroups"
                  role="button"
                  tabindex="-1"
                >
                  Add
                </gr-button>
                <table id="includedGroups">
                  <tbody>
                    <tr class="headerRow">
                      <th class="groupNameHeader">Group Name</th>
                      <th class="descriptionHeader">Description</th>
                      <th class="deleteIncludedHeader">Delete Group</th>
                    </tr>
                  </tbody>
                  <tbody>
                    <tr>
                      <td class="nameColumn">
                        <a href="https://group/url" rel="noopener">
                          testName
                        </a>
                      </td>
                      <td></td>
                      <td class="deleteColumn">
                        <gr-button
                          aria-disabled="false"
                          class="deleteIncludedGroupButton"
                          data-index="0"
                          role="button"
                          tabindex="0"
                        >
                          Delete
                        </gr-button>
                      </td>
                    </tr>
                    <tr>
                      <td class="nameColumn">
                        <a href="https://test/site/group/url" rel="noopener">
                          testName2
                        </a>
                      </td>
                      <td></td>
                      <td class="deleteColumn">
                        <gr-button
                          aria-disabled="false"
                          class="deleteIncludedGroupButton"
                          data-index="1"
                          role="button"
                          tabindex="0"
                        >
                          Delete
                        </gr-button>
                      </td>
                    </tr>
                    <tr>
                      <td class="nameColumn">
                        <a href="https://test/site/group/url" rel="noopener">
                          testName3
                        </a>
                      </td>
                      <td></td>
                      <td class="deleteColumn">
                        <gr-button
                          aria-disabled="false"
                          class="deleteIncludedGroupButton"
                          data-index="2"
                          role="button"
                          tabindex="0"
                        >
                          Delete
                        </gr-button>
                      </td>
                    </tr>
                  </tbody>
                </table>
              </fieldset>
            </div>
          </div>
        </div>
        <gr-overlay
          aria-hidden="true"
          id="overlay"
          style="outline: none; display: none;"
          tabindex="-1"
          with-backdrop=""
        >
          <gr-confirm-delete-item-dialog class="confirmDialog">
          </gr-confirm-delete-item-dialog>
        </gr-overlay>
      `
    );
  });

  test('includedGroups', () => {
    assert.equal(element.includedGroups!.length, 3);
    assert.equal(
      queryAll<HTMLAnchorElement>(element, '.nameColumn a')[0].href,
      includedGroups[0].url
    );
    assert.equal(
      queryAll<HTMLAnchorElement>(element, '.nameColumn a')[1].href,
      'https://test/site/group/url'
    );
    assert.equal(
      queryAll<HTMLAnchorElement>(element, '.nameColumn a')[2].href,
      'https://test/site/group/url'
    );
  });

  test('save members correctly', async () => {
    element.groupOwner = true;

    const memberName = 'test-admin';

    const saveStub = stubRestApi('saveGroupMember').callsFake(() =>
      Promise.resolve({})
    );

    const button = queryAndAssert<GrButton>(element, '#saveGroupMember');

    assert.isTrue(button.hasAttribute('disabled'));

    const groupMemberSearchInput = queryAndAssert<GrAutocomplete>(
      element,
      '#groupMemberSearchInput'
    );
    groupMemberSearchInput.text = memberName;
    groupMemberSearchInput.value = '1234';

    await waitUntil(() => !button.hasAttribute('disabled'));

    return element.handleSavingGroupMember().then(() => {
      assert.isTrue(button.hasAttribute('disabled'));
      assert.isFalse(
        queryAndAssert<HTMLHeadingElement>(
          element,
          '#Title'
        ).classList.contains('edited')
      );
      assert.equal(saveStub.lastCall.args[0], 'Administrators');
      assert.equal(saveStub.lastCall.args[1], 1234);
    });
  });

  test('save included groups correctly', async () => {
    element.groupOwner = true;

    const includedGroupName = 'testName';

    const saveIncludedGroupStub = stubRestApi('saveIncludedGroup').callsFake(
      () => Promise.resolve({id: '0' as GroupId})
    );

    const button = queryAndAssert<GrButton>(element, '#saveIncludedGroups');

    await waitUntil(() => button.hasAttribute('disabled'));

    const includedGroupSearchInput = queryAndAssert<GrAutocomplete>(
      element,
      '#includedGroupSearchInput'
    );
    includedGroupSearchInput.text = includedGroupName;
    includedGroupSearchInput.value = 'testId';

    await waitUntil(() => !button.hasAttribute('disabled'));

    return element.handleSavingIncludedGroups().then(() => {
      assert.isTrue(button.hasAttribute('disabled'));
      assert.isFalse(
        queryAndAssert<HTMLHeadingElement>(
          element,
          '#Title'
        ).classList.contains('edited')
      );
      assert.equal(saveIncludedGroupStub.lastCall.args[0], 'Administrators');
      assert.equal(saveIncludedGroupStub.lastCall.args[1], 'testId');
    });
  });

  test('add included group 404 shows helpful error text', async () => {
    element.groupOwner = true;
    element.groupName = 'test' as GroupName;

    const memberName = 'bad-name';
    const alertStub = sinon.stub();
    element.addEventListener(EventType.SHOW_ALERT, alertStub);
    const errorResponse = {...new Response(), status: 404, ok: false};
    stubRestApi('saveIncludedGroup').callsFake((_, _non, errFn) => {
      if (errFn !== undefined) {
        errFn(errorResponse);
      } else {
        assert.fail('errFn is undefined');
      }
      return Promise.resolve(undefined);
    });

    const groupMemberSearchInput = queryAndAssert<GrAutocomplete>(
      element,
      '#groupMemberSearchInput'
    );
    groupMemberSearchInput.text = memberName;
    groupMemberSearchInput.value = '1234';

    await element.updateComplete;
    element.handleSavingIncludedGroups().then(() => {
      assert.isTrue(alertStub.called);
    });
  });

  test('add included group network-error throws an exception', async () => {
    element.groupOwner = true;
    const memberName = 'bad-name';
    stubRestApi('saveIncludedGroup').throws(new Error());

    const groupMemberSearchInput = queryAndAssert<GrAutocomplete>(
      element,
      '#groupMemberSearchInput'
    );
    groupMemberSearchInput.text = memberName;
    groupMemberSearchInput.value = '1234';

    let exceptionThrown = false;
    try {
      await element.handleSavingIncludedGroups();
    } catch (e) {
      exceptionThrown = true;
    }
    assert.isTrue(exceptionThrown);
  });

  test('getAccountSuggestions empty', async () => {
    const accounts = await getAccountSuggestions(
      'nonexistent',
      getAppContext().restApiService,
      createServerInfo()
    );
    assert.equal(accounts.length, 0);
  });

  test('getAccountSuggestions non-empty', async () => {
    const accounts = await getAccountSuggestions(
      'test-',
      getAppContext().restApiService,
      createServerInfo()
    );
    assert.equal(accounts.length, 3);
    assert.equal(
      accounts[0].name,
      'display-test-account <test.account@example.com>'
    );
    assert.equal(accounts[1].name, 'test-admin <test.admin@example.com>');
    assert.equal(accounts[2].name, 'test-git');
  });

  test('getGroupSuggestions empty', async () => {
    const groups = await element.getGroupSuggestions('nonexistent');

    assert.equal(groups.length, 0);
  });

  test('getGroupSuggestions non-empty', async () => {
    const groups = await element.getGroupSuggestions('test');

    assert.equal(groups.length, 2);
    assert.equal(groups[0].name, 'test-admin');
    assert.equal(groups[1].name, 'test/Administrator (admin)');
  });

  test('delete member', () => {
    const deleteBtns = queryAll<GrButton>(element, '.deleteMembersButton');
    deleteBtns[0].click();
    assert.equal(element.itemId, 1000097 as AccountId);
    assert.equal(element.itemName, 'jane');
    deleteBtns[1].click();
    assert.equal(element.itemId, 1000096 as AccountId);
    assert.equal(element.itemName, 'Test User');
    deleteBtns[2].click();
    assert.equal(element.itemId, 1000095 as AccountId);
    assert.equal(element.itemName, 'Gerrit');
    deleteBtns[3].click();
    assert.equal(element.itemId, 1000098 as AccountId);
    assert.equal(element.itemName, '1000098');
  });

  test('delete included groups', () => {
    const deleteBtns = queryAll<GrButton>(
      element,
      '.deleteIncludedGroupButton'
    );
    deleteBtns[0].click();
    assert.equal(element.itemId, 'testId' as GroupId);
    assert.equal(element.itemName, 'testName');
    deleteBtns[1].click();
    assert.equal(element.itemId, 'testId2' as GroupId);
    assert.equal(element.itemName, 'testName2');
    deleteBtns[2].click();
    assert.equal(element.itemId, 'testId3' as GroupId);
    assert.equal(element.itemName, 'testName3');
  });

  test('computeGroupUrl', () => {
    assert.isUndefined(element.computeGroupUrl(undefined));

    let url = '#/admin/groups/uuid-529b3c2605bb1029c8146f9de4a91c776fe64498';
    assert.equal(
      element.computeGroupUrl(url),
      'https://test/site/admin/groups/' +
        'uuid-529b3c2605bb1029c8146f9de4a91c776fe64498'
    );

    url =
      'https://gerrit.local/admin/groups/' +
      'uuid-529b3c2605bb1029c8146f9de4a91c776fe64498';
    assert.equal(element.computeGroupUrl(url), url);
  });

  test('fires page-error', async () => {
    groupStub.restore();

    element.groupId = 'testId1' as GroupId;

    const response = {...new Response(), status: 404};
    stubRestApi('getGroupConfig').callsFake((_, errFn) => {
      if (errFn !== undefined) {
        errFn(response);
      }
      return Promise.resolve(undefined);
    });
    const promise = mockPromise();
    addListenerForTest(document, 'page-error', e => {
      assert.deepEqual((e as PageErrorEvent).detail.response, response);
      promise.resolve();
    });

    element.loadGroupDetails();
    await promise;
  });

  test('_computeItemName', () => {
    assert.equal(element.computeItemTypeName(ItemType.MEMBER), 'Member');
    assert.equal(
      element.computeItemTypeName(ItemType.INCLUDED_GROUP),
      'Included Group'
    );
  });
});
