/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import '../../../test/common-test-setup';
import './gr-permission';
import {GrPermission} from './gr-permission';
import {query, stubRestApi, waitEventLoop} from '../../../test/test-utils';
import {GitRef, GroupId, GroupName} from '../../../types/common';
import {PermissionAction} from '../../../constants/constants';
import {
  AutocompleteCommitEventDetail,
  GrAutocomplete,
} from '../../shared/gr-autocomplete/gr-autocomplete';
import {queryAndAssert} from '../../../test/test-utils';
import {GrRuleEditor} from '../gr-rule-editor/gr-rule-editor';
import {GrButton} from '../../shared/gr-button/gr-button';
import {fixture, html, assert} from '@open-wc/testing';
import {PaperToggleButtonElement} from '@polymer/paper-toggle-button';

suite('gr-permission tests', () => {
  let element: GrPermission;

  setup(async () => {
    element = await fixture(html`<gr-permission></gr-permission>`);
    stubRestApi('getSuggestedGroups').returns(
      Promise.resolve({
        Administrators: {
          id: '4c97682e6ce61b7247f3381b6f1789356666de7f' as GroupId,
        },
        'Anonymous Users': {
          id: 'global%3AAnonymous-Users' as GroupId,
        },
      })
    );
  });

  suite('unit tests', () => {
    test('sortPermission', async () => {
      const permission = {
        id: 'submit' as GitRef,
        value: {
          rules: {
            'global:Project-Owners': {
              action: PermissionAction.ALLOW,
              force: false,
            },
            '4c97682e6ce6b7247f3381b6f1789356666de7f': {
              action: PermissionAction.ALLOW,
              force: false,
            },
          },
        },
      };

      const expectedRules = [
        {
          id: '4c97682e6ce6b7247f3381b6f1789356666de7f' as GitRef,
          value: {action: PermissionAction.ALLOW, force: false},
        },
        {
          id: 'global:Project-Owners' as GitRef,
          value: {action: PermissionAction.ALLOW, force: false},
        },
      ];

      element.sortPermission(permission);
      await element.updateComplete;
      assert.deepEqual(element.rules, expectedRules);
    });

    test('computeLabel and computeLabelValues', async () => {
      const labels = {
        'Code-Review': {
          default_value: 0,
          values: {
            ' 0': 'No score',
            '-1': 'I would prefer this is not submitted as is',
            '-2': 'This shall not be submitted',
            '+1': 'Looks good to me, but someone else must approve',
            '+2': 'Looks good to me, approved',
          },
        },
      };
      let permission = {
        id: 'label-Code-Review' as GitRef,
        value: {
          label: 'Code-Review',
          rules: {
            'global:Project-Owners': {
              action: PermissionAction.ALLOW,
              force: false,
              min: -2,
              max: 2,
            },
            '4c97682e6ce6b7247f3381b6f1789356666de7f': {
              action: PermissionAction.ALLOW,
              force: false,
              min: -2,
              max: 2,
            },
          },
        },
      };

      const expectedLabelValues = [
        {value: -2, text: 'This shall not be submitted'},
        {value: -1, text: 'I would prefer this is not submitted as is'},
        {value: 0, text: 'No score'},
        {value: 1, text: 'Looks good to me, but someone else must approve'},
        {value: 2, text: 'Looks good to me, approved'},
      ];

      const expectedLabel = {
        name: 'Code-Review',
        values: expectedLabelValues,
      };

      element.permission = permission;
      element.labels = labels;
      await element.updateComplete;

      assert.deepEqual(
        element.computeLabelValues(labels['Code-Review'].values),
        expectedLabelValues
      );

      assert.deepEqual(element.computeLabel(), expectedLabel);

      permission = {
        id: 'label-reviewDB' as GitRef,
        value: {
          label: 'reviewDB',
          rules: {
            'global:Project-Owners': {
              action: PermissionAction.ALLOW,
              force: false,
              min: 0,
              max: 0,
            },
            '4c97682e6ce6b7247f3381b6f1789356666de7f': {
              action: PermissionAction.ALLOW,
              force: false,
              min: 0,
              max: 0,
            },
          },
        },
      };

      element.permission = permission;
      await element.updateComplete;

      assert.isNotOk(element.computeLabel());
    });

    test('computeSectionClass', async () => {
      let deleted = true;
      let editing = false;
      assert.equal(element.computeSectionClass(editing, deleted), 'deleted');

      deleted = false;
      assert.equal(element.computeSectionClass(editing, deleted), '');

      editing = true;
      assert.equal(element.computeSectionClass(editing, deleted), 'editing');

      deleted = true;
      assert.equal(
        element.computeSectionClass(editing, deleted),
        'editing deleted'
      );
    });

    test('computeGroupName', async () => {
      const groups = {
        abc123: {id: '1' as GroupId, name: 'test group' as GroupName},
        bcd234: {id: '1' as GroupId},
      };
      assert.equal(
        element.computeGroupName(groups, 'abc123' as GitRef),
        'test group' as GroupName
      );
      assert.equal(
        element.computeGroupName(groups, 'bcd234' as GitRef),
        'bcd234' as GroupName
      );
    });

    test('computeGroupsWithRules', async () => {
      const rules = [
        {
          id: '4c97682e6ce6b7247f3381b6f1789356666de7f' as GitRef,
          value: {action: PermissionAction.ALLOW, force: false},
        },
        {
          id: 'global:Project-Owners' as GitRef,
          value: {action: PermissionAction.ALLOW, force: false},
        },
      ];
      const groupsWithRules = {
        '4c97682e6ce6b7247f3381b6f1789356666de7f': true,
        'global:Project-Owners': true,
      };
      assert.deepEqual(element.computeGroupsWithRules(rules), groupsWithRules);
    });

    test('getGroupSuggestions without existing rules', async () => {
      element.groupsWithRules = {};
      await element.updateComplete;

      const groups = await element.getGroupSuggestions();
      assert.deepEqual(groups, [
        {
          name: 'Administrators',
          value: '4c97682e6ce61b7247f3381b6f1789356666de7f',
        },
        {
          name: 'Anonymous Users',
          value: 'global%3AAnonymous-Users',
        },
      ]);
    });

    test('getGroupSuggestions with existing rules filters them', async () => {
      element.groupsWithRules = {
        '4c97682e6ce61b7247f3381b6f1789356666de7f': true,
      };
      await element.updateComplete;

      const groups = await element.getGroupSuggestions();
      assert.deepEqual(groups, [
        {
          name: 'Anonymous Users',
          value: 'global%3AAnonymous-Users',
        },
      ]);
    });

    test('handleRemovePermission', async () => {
      element.editing = true;
      element.permission = {id: 'test' as GitRef, value: {rules: {}}};
      element.handleRemovePermission();
      await element.updateComplete;

      assert.isTrue(element.deleted);
      assert.isTrue(element.permission.value.deleted);

      element.editing = false;
      await element.updateComplete;
      assert.isFalse(element.deleted);
      assert.isNotOk(element.permission.value.deleted);
    });

    test('handleUndoRemove', async () => {
      element.permission = {
        id: 'test' as GitRef,
        value: {deleted: true, rules: {}},
      };
      element.handleUndoRemove();
      await element.updateComplete;

      assert.isFalse(element.deleted);
      assert.isNotOk(element.permission.value.deleted);
    });

    test('computeHasRange', async () => {
      assert.isTrue(element.computeHasRange('Query Limit'));

      assert.isTrue(element.computeHasRange('Batch Changes Limit'));

      assert.isFalse(element.computeHasRange('test'));
    });
  });

  suite('interactions', () => {
    setup(async () => {
      sinon.spy(element, 'computeLabel');
      element.name = 'Priority';
      element.section = 'refs/*' as GitRef;
      element.labels = {
        'Code-Review': {
          values: {
            ' 0': 'No score',
            '-1': 'I would prefer this is not submitted as is',
            '-2': 'This shall not be submitted',
            '+1': 'Looks good to me, but someone else must approve',
            '+2': 'Looks good to me, approved',
          },
          default_value: 0,
        },
      };
      element.permission = {
        id: 'label-Code-Review' as GitRef,
        value: {
          label: 'Code-Review',
          rules: {
            'global:Project-Owners': {
              action: PermissionAction.ALLOW,
              force: false,
              min: -2,
              max: 2,
            },
            '4c97682e6ce6b7247f3381b6f1789356666de7f': {
              action: PermissionAction.ALLOW,
              force: false,
              min: -2,
              max: 2,
            },
          },
        },
      };
      element.setupValues();
      await element.updateComplete;
      await waitEventLoop();
    });

    test('render', () => {
      assert.shadowDom.equal(
        element,
        /* HTML */ `
          <section class="gr-form-styles" id="permission">
            <div id="mainContainer">
              <div class="header">
                <span class="title"> Priority </span>
                <div class="right">
                  <paper-toggle-button
                    aria-disabled="true"
                    aria-pressed="false"
                    disabled=""
                    id="exclusiveToggle"
                    role="button"
                    style="pointer-events: none; touch-action: none;"
                    tabindex="-1"
                    toggles=""
                  >
                  </paper-toggle-button>
                  Not Exclusive
                  <gr-button
                    aria-disabled="false"
                    id="removeBtn"
                    link=""
                    role="button"
                    tabindex="0"
                  >
                    Remove
                  </gr-button>
                </div>
              </div>
              <div class="rules">
                <gr-rule-editor> </gr-rule-editor>
                <gr-rule-editor> </gr-rule-editor>
                <div id="addRule">
                  <gr-autocomplete
                    id="groupAutocomplete"
                    placeholder="Add group"
                  >
                  </gr-autocomplete>
                </div>
              </div>
            </div>
            <div id="deletedContainer">
              <span> Priority was deleted </span>
              <gr-button
                aria-disabled="false"
                id="undoRemoveBtn"
                link=""
                role="button"
                tabindex="0"
              >
                Undo
              </gr-button>
            </div>
          </section>
        `,
        // touch-action varies on paper-toggle-button between local and CI
        {
          ignoreAttributes: [
            {tags: ['paper-toggle-button'], attributes: ['style']},
          ],
        }
      );
    });

    test('adding a rule', async () => {
      element.name = 'Priority';
      element.section = 'refs/*' as GitRef;
      element.groups = {};
      await element.updateComplete;

      queryAndAssert<GrAutocomplete>(element, '#groupAutocomplete').text =
        'ldap/tests te.st';
      const e = {
        detail: {
          value: 'ldap:CN=test+te.st',
        },
      } as CustomEvent<AutocompleteCommitEventDetail>;
      element.editing = true;
      assert.equal(element.rules!.length, 2);
      assert.equal(Object.keys(element.groupsWithRules!).length, 2);
      await element.handleAddRuleItem(e);
      assert.deepEqual(element.groups, {
        'ldap:CN=test te.st': {
          name: 'ldap/tests te.st',
        },
      });
      assert.equal(element.rules!.length, 3);
      assert.equal(Object.keys(element.groupsWithRules!).length, 3);
      assert.deepEqual(element.permission!.value.rules['ldap:CN=test te.st'], {
        action: PermissionAction.ALLOW,
        min: -2,
        max: 2,
        added: true,
      });
      assert.equal(
        queryAndAssert<GrAutocomplete>(element, '#groupAutocomplete').text,
        ''
      );
      // New rule should be removed if cancel from editing.
      element.editing = false;
      await element.updateComplete;
      assert.equal(element.rules!.length, 2);
      assert.equal(Object.keys(element.permission!.value.rules).length, 2);
    });

    test('removing an added rule', async () => {
      element.name = 'Priority';
      element.section = 'refs/*' as GitRef;
      element.groups = {};
      await element.updateComplete;
      queryAndAssert<GrAutocomplete>(element, '#groupAutocomplete').text =
        'new group name';
      assert.equal(element.rules!.length, 2);
      queryAndAssert<GrRuleEditor>(element, 'gr-rule-editor').dispatchEvent(
        new CustomEvent('added-rule-removed', {
          composed: true,
          bubbles: true,
        })
      );
      await waitEventLoop();
      assert.equal(element.rules!.length, 1);
    });

    test('removing an added permission', async () => {
      const removeStub = sinon.stub();
      element.addEventListener('added-permission-removed', removeStub);
      element.editing = true;
      element.name = 'Priority';
      element.section = 'refs/*' as GitRef;
      element.permission!.value.added = true;
      await element.updateComplete;
      queryAndAssert<GrButton>(element, '#removeBtn').click();
      await element.updateComplete;
      assert.isTrue(removeStub.called);
    });

    test('removing the permission', async () => {
      element.editing = true;
      element.name = 'Priority';
      element.section = 'refs/*' as GitRef;
      await element.updateComplete;

      const removeStub = sinon.stub();
      element.addEventListener('added-permission-removed', removeStub);

      assert.isFalse(
        queryAndAssert(element, '#permission').classList.contains('deleted')
      );
      assert.isFalse(element.deleted);
      queryAndAssert<GrButton>(element, '#removeBtn').click();
      await element.updateComplete;
      assert.isTrue(
        queryAndAssert(element, '#permission').classList.contains('deleted')
      );
      assert.isTrue(element.deleted);
      queryAndAssert<GrButton>(element, '#undoRemoveBtn').click();

      await element.updateComplete;
      assert.isFalse(
        queryAndAssert(element, '#permission').classList.contains('deleted')
      );
      assert.isFalse(element.deleted);
      assert.isFalse(removeStub.called);
    });

    test('modify a permission', async () => {
      element.editing = true;
      element.name = 'Priority';
      element.section = 'refs/*' as GitRef;
      await element.updateComplete;

      assert.isFalse(element.originalExclusiveValue);
      assert.isNotOk(element.permission!.value.modified);
      queryAndAssert<PaperToggleButtonElement>(
        element,
        '#exclusiveToggle'
      ).click();
      await element.updateComplete;
      assert.isTrue(element.permission!.value.exclusive);
      assert.isTrue(element.permission!.value.modified);
      assert.isFalse(element.originalExclusiveValue);
      element.editing = false;
      await element.updateComplete;
      assert.isFalse(element.permission!.value.exclusive);
    });

    test('modifying emits access-modified event', async () => {
      const modifiedHandler = sinon.stub();
      element.editing = true;
      element.name = 'Priority';
      element.section = 'refs/*' as GitRef;
      element.permission = {id: '0' as GitRef, value: {rules: {}}};
      element.addEventListener('access-modified', modifiedHandler);
      await element.updateComplete;
      assert.isNotOk(element.permission.value.modified);
      queryAndAssert<PaperToggleButtonElement>(
        element,
        '#exclusiveToggle'
      ).click();
      await element.updateComplete;
      assert.isTrue(element.permission.value.modified);
      assert.isTrue(modifiedHandler.called);
    });

    test('Exclusive hidden for owner permission', async () => {
      queryAndAssert(element, '#exclusiveToggle');

      element.permission!.id = 'owner' as GitRef;
      element.requestUpdate();
      await element.updateComplete;

      assert.notOk(query(element, '#exclusiveToggle'));
    });

    test('Exclusive hidden for any global permissions', async () => {
      queryAndAssert(element, '#exclusiveToggle');

      element.section = 'GLOBAL_CAPABILITIES' as GitRef;
      await element.updateComplete;

      assert.notOk(query(element, '#exclusiveToggle'));
    });
  });
});
