/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import '../../../test/common-test-setup';
import './gr-access-section';
import {
  AccessPermissions,
  toSortedPermissionsArray,
} from '../../../utils/access-util';
import {GrAccessSection} from './gr-access-section';
import {GitRef} from '../../../types/common';
import {queryAndAssert} from '../../../utils/common-util';
import {GrButton} from '../../shared/gr-button/gr-button';
import {fixture, html, assert} from '@open-wc/testing';

suite('gr-access-section tests', () => {
  let element: GrAccessSection;

  setup(async () => {
    element = await fixture<GrAccessSection>(html`
      <gr-access-section></gr-access-section>
    `);
  });

  suite('unit tests', () => {
    setup(async () => {
      element.section = {
        id: 'refs/*' as GitRef,
        value: {
          permissions: {
            read: {
              rules: {},
            },
          },
        },
      };
      element.capabilities = {
        accessDatabase: {
          id: 'accessDatabase',
          name: 'Access Database',
        },
        administrateServer: {
          id: 'administrateServer',
          name: 'Administrate Server',
        },
        batchChangesLimit: {
          id: 'batchChangesLimit',
          name: 'Batch Changes Limit',
        },
        createAccount: {
          id: 'createAccount',
          name: 'Create Account',
        },
      };
      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.updateSection();
      await element.updateComplete;
    });

    test('render', () => {
      assert.shadowDom.equal(
        element,
        /* HTML */ `
          <fieldset class="gr-form-styles" id="section">
            <div id="mainContainer">
              <div class="header">
                <div class="name">
                  <h3 class="heading-3">Reference: refs/*</h3>
                  <gr-button
                    aria-disabled="false"
                    id="editBtn"
                    link=""
                    role="button"
                    tabindex="0"
                  >
                    <gr-icon icon="edit" id="icon" small filled></gr-icon>
                  </gr-button>
                </div>
                <iron-input class="editRefInput">
                  <input class="editRefInput" type="text" />
                </iron-input>
                <gr-button
                  aria-disabled="false"
                  id="deleteBtn"
                  link=""
                  role="button"
                  tabindex="0"
                >
                  Remove
                </gr-button>
              </div>
              <div class="sectionContent">
                <gr-permission> </gr-permission>
                <div id="addPermission">
                  Add permission:
                  <select id="permissionSelect">
                    <option value="label-Code-Review">Label Code-Review</option>
                    <option value="labelAs-Code-Review">
                      Label Code-Review (On Behalf Of)
                    </option>
                    <option value="abandon">Abandon</option>
                    <option value="addPatchSet">Add Patch Set</option>
                    <option value="create">Create Reference</option>
                    <option value="createSignedTag">Create Signed Tag</option>
                    <option value="createTag">Create Annotated Tag</option>
                    <option value="delete">Delete Reference</option>
                    <option value="deleteChanges">Delete Changes</option>
                    <option value="deleteOwnChanges">Delete Own Changes</option>
                    <option value="editHashtags">Edit Hashtags</option>
                    <option value="editTopicName">Edit Topic Name</option>
                    <option value="forgeAuthor">Forge Author Identity</option>
                    <option value="forgeCommitter">
                      Forge Committer Identity
                    </option>
                    <option value="forgeServerAsCommitter">
                      Forge Server Identity
                    </option>
                    <option value="owner">Owner</option>
                    <option value="push">Push</option>
                    <option value="pushMerge">Push Merge Commit</option>
                    <option value="rebase">Rebase</option>
                    <option value="removeReviewer">Remove Reviewer</option>
                    <option value="revert">Revert</option>
                    <option value="submit">Submit</option>
                    <option value="submitAs">Submit (On Behalf Of)</option>
                    <option value="toggleWipState">
                      Toggle Work In Progress State
                    </option>
                    <option value="viewPrivateChanges">
                      View Private Changes
                    </option>
                  </select>
                  <gr-button
                    aria-disabled="false"
                    id="addBtn"
                    link=""
                    role="button"
                    tabindex="0"
                  >
                    Add
                  </gr-button>
                </div>
              </div>
            </div>
            <div id="deletedContainer">
              <span> Reference: refs/* was deleted </span>
              <gr-button
                aria-disabled="false"
                id="undoRemoveBtn"
                link=""
                role="button"
                tabindex="0"
              >
                Undo
              </gr-button>
            </div>
          </fieldset>
        `
      );
    });

    test('updateSection', () => {
      // updateSection was called in setup, so just make assertions.
      const expectedPermissions = [
        {
          id: 'read' as GitRef,
          value: {
            rules: {},
          },
        },
      ];
      assert.deepEqual(element.permissions, expectedPermissions);
      assert.equal(element.originalId, element.section!.id);
    });

    test('computeLabelOptions', () => {
      const expectedLabelOptions = [
        {
          id: 'label-Code-Review',
          value: {
            name: 'Label Code-Review',
            id: 'label-Code-Review',
          },
        },
        {
          id: 'labelAs-Code-Review',
          value: {
            name: 'Label Code-Review (On Behalf Of)',
            id: 'labelAs-Code-Review',
          },
        },
      ];

      assert.deepEqual(element.computeLabelOptions(), expectedLabelOptions);
    });

    test('handleAccessSaved', () => {
      assert.equal(element.originalId, 'refs/*' as GitRef);
      element.section!.id = 'refs/for/bar' as GitRef;
      element.handleAccessSaved();
      assert.equal(element.originalId, 'refs/for/bar' as GitRef);
    });

    test('computePermissions', () => {
      const capabilities = {
        push: {
          id: '',
          name: '',
          rules: {},
        },
        read: {
          id: '',
          name: '',
          rules: {},
        },
      };

      const expectedPermissions = [
        {
          id: 'push',
          value: {
            id: '',
            name: '',
            rules: {},
          },
        },
      ];
      const labelOptions = [
        {
          id: 'label-Code-Review',
          value: {
            name: 'Label Code-Review',
            id: 'label-Code-Review',
          },
        },
        {
          id: 'labelAs-Code-Review',
          value: {
            name: 'Label Code-Review (On Behalf Of)',
            id: 'labelAs-Code-Review',
          },
        },
      ];

      element.section = {
        id: 'refs/*' as GitRef,
        value: {
          permissions: {
            read: {
              rules: {},
            },
          },
        },
      };

      // For global capabilities, just return the sorted array filtered by
      // existing permissions.
      element.section = {
        id: 'GLOBAL_CAPABILITIES' as GitRef,
        value: {
          permissions: {
            read: {
              rules: {},
            },
          },
        },
      };
      element.capabilities = capabilities;
      assert.deepEqual(element.computePermissions(), expectedPermissions);

      // For everything else, include possible label values before filtering.
      element.section.id = 'refs/for/*' as GitRef;
      assert.deepEqual(
        element.computePermissions(),
        labelOptions
          .concat(toSortedPermissionsArray(AccessPermissions))
          .filter(permission => permission.id !== 'read')
      );
    });

    test('computePermissionName', () => {
      element.section = {
        id: 'GLOBAL_CAPABILITIES' as GitRef,
        value: {
          permissions: {
            read: {
              rules: {},
            },
          },
        },
      };

      let permission;

      permission = {
        id: 'administrateServer' as GitRef,
        value: {rules: {}},
      };
      assert.equal(
        element.computePermissionName(permission),
        element.capabilities![permission.id].name
      );

      permission = {
        id: 'non-existent' as GitRef,
        value: {rules: {}},
      };
      assert.isUndefined(element.computePermissionName(permission));

      element.section.id = 'refs/for/*' as GitRef;
      permission = {
        id: 'abandon' as GitRef,
        value: {rules: {}},
      };

      assert.equal(
        element.computePermissionName(permission),
        AccessPermissions[permission.id].name
      );

      element.section.id = 'refs/for/*' as GitRef;
      permission = {
        id: 'label-Code-Review' as GitRef,
        value: {
          label: 'Code-Review',
          rules: {},
        },
      };

      assert.equal(
        element.computePermissionName(permission),
        'Label Code-Review'
      );

      permission = {
        id: 'labelAs-Code-Review' as GitRef,
        value: {
          label: 'Code-Review',
          rules: {},
        },
      };

      assert.equal(
        element.computePermissionName(permission),
        'Label Code-Review(On Behalf Of)'
      );
    });

    test('computeSectionName', () => {
      // When computing the section name for an undefined name, it means a
      // new section is being added. In this case, it should default to
      // 'refs/heads/*'.
      element.editingRef = false;
      element.section!.id = '' as GitRef;
      assert.equal(element.computeSectionName(), 'Reference: refs/heads/*');
      assert.isTrue(element.editingRef);
      assert.equal(element.section!.id, 'refs/heads/*');

      // Reset editing to false.
      element.editingRef = false;
      element.section!.id = 'GLOBAL_CAPABILITIES' as GitRef;
      assert.equal(element.computeSectionName(), 'Global Capabilities');
      assert.isFalse(element.editingRef);

      element.section!.id = 'refs/for/*' as GitRef;
      assert.equal(element.computeSectionName(), 'Reference: refs/for/*');
      assert.isFalse(element.editingRef);
    });

    test('editReference', () => {
      element.editReference();
      assert.isTrue(element.editingRef);
    });

    test('computeSectionClass', () => {
      element.editingRef = false;
      element.canUpload = false;
      element.ownerOf = [];
      element.editing = false;
      element.deleted = false;
      assert.equal(element.computeSectionClass(), '');

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

      element.ownerOf = ['refs/*' as GitRef];
      assert.equal(element.computeSectionClass(), 'editing');

      element.ownerOf = [];
      element.canUpload = true;
      assert.equal(element.computeSectionClass(), 'editing');

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

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

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

  suite('interactive tests', () => {
    setup(() => {
      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,
        },
      };
    });
    suite('Global section', () => {
      setup(async () => {
        element.section = {
          id: 'GLOBAL_CAPABILITIES' as GitRef,
          value: {
            permissions: {
              accessDatabase: {
                rules: {},
              },
            },
          },
        };
        element.capabilities = {
          accessDatabase: {
            id: 'accessDatabase',
            name: 'Access Database',
          },
          administrateServer: {
            id: 'administrateServer',
            name: 'Administrate Server',
          },
          batchChangesLimit: {
            id: 'batchChangesLimit',
            name: 'Batch Changes Limit',
          },
          createAccount: {
            id: 'createAccount',
            name: 'Create Account',
          },
        };
        element.updateSection();
        await element.updateComplete;
      });

      test('classes are assigned correctly', () => {
        assert.isFalse(
          queryAndAssert<HTMLFieldSetElement>(
            element,
            '#section'
          ).classList.contains('editing')
        );
        assert.isFalse(
          queryAndAssert<HTMLFieldSetElement>(
            element,
            '#section'
          ).classList.contains('deleted')
        );
        assert.isTrue(
          queryAndAssert<GrButton>(element, '#editBtn').classList.contains(
            'global'
          )
        );
        element.editing = true;
        element.canUpload = true;
        element.ownerOf = [];
        assert.equal(
          getComputedStyle(queryAndAssert<GrButton>(element, '#editBtn'))
            .display,
          'none'
        );
      });
    });

    suite('Non-global section', () => {
      setup(async () => {
        element.section = {
          id: 'refs/*' as GitRef,
          value: {
            permissions: {
              read: {
                rules: {},
              },
            },
          },
        };
        element.capabilities = {};
        element.updateSection();
        await element.updateComplete;
      });

      test('classes are assigned correctly', async () => {
        assert.isFalse(
          queryAndAssert<HTMLFieldSetElement>(
            element,
            '#section'
          ).classList.contains('editing')
        );
        assert.isFalse(
          queryAndAssert<HTMLFieldSetElement>(
            element,
            '#section'
          ).classList.contains('deleted')
        );
        assert.isFalse(
          queryAndAssert<GrButton>(element, '#editBtn').classList.contains(
            'global'
          )
        );
        element.editing = true;
        element.canUpload = true;
        element.ownerOf = [];
        await element.updateComplete;
        assert.notEqual(
          getComputedStyle(queryAndAssert<GrButton>(element, '#editBtn'))
            .display,
          'none'
        );
      });

      test('add permission', async () => {
        element.editing = true;
        queryAndAssert<HTMLSelectElement>(element, '#permissionSelect').value =
          'label-Code-Review';
        assert.equal(element.permissions!.length, 1);
        assert.equal(Object.keys(element.section!.value.permissions).length, 1);
        queryAndAssert<GrButton>(element, '#addBtn').click();
        await element.updateComplete;

        // The permission is added to both the permissions array and also
        // the section's permission object.
        assert.equal(element.permissions!.length, 2);
        let permission;

        permission = {
          id: 'label-Code-Review' as GitRef,
          value: {
            added: true,
            label: 'Code-Review',
            rules: {},
          },
        };
        assert.equal(element.permissions!.length, 2);
        assert.deepEqual(element.permissions![1], permission);
        assert.equal(Object.keys(element.section!.value.permissions).length, 2);
        assert.deepEqual(
          element.section!.value.permissions['label-Code-Review'],
          permission.value
        );

        queryAndAssert<HTMLSelectElement>(element, '#permissionSelect').value =
          'abandon';
        queryAndAssert<GrButton>(element, '#addBtn').click();
        await element.updateComplete;

        permission = {
          id: 'abandon' as GitRef,
          value: {
            added: true,
            rules: {},
          },
        };

        assert.equal(element.permissions!.length, 3);
        assert.deepEqual(element.permissions![2], permission);
        assert.equal(Object.keys(element.section!.value.permissions).length, 3);
        assert.deepEqual(
          element.section!.value.permissions['abandon'],
          permission.value
        );

        // Unsaved changes are discarded when editing is cancelled.
        element.editing = false;
        await element.updateComplete;
        assert.equal(element.permissions!.length, 1);
        assert.equal(Object.keys(element.section!.value.permissions).length, 1);
      });

      test('edit section reference', async () => {
        element.canUpload = true;
        element.ownerOf = [];
        element.section = {
          id: 'refs/for/bar' as GitRef,
          value: {permissions: {}},
        };
        await element.updateComplete;
        assert.isFalse(
          queryAndAssert<HTMLFieldSetElement>(
            element,
            '#section'
          ).classList.contains('editing')
        );
        element.editing = true;
        await element.updateComplete;
        assert.isTrue(
          queryAndAssert<HTMLFieldSetElement>(
            element,
            '#section'
          ).classList.contains('editing')
        );
        assert.isFalse(element.editingRef);
        queryAndAssert<GrButton>(element, '#editBtn').click();
        element.editRefInput().bindValue = 'new/ref';
        await element.updateComplete;
        assert.equal(element.section.id, 'new/ref');
        assert.isTrue(element.editingRef);
        assert.isTrue(
          queryAndAssert<HTMLFieldSetElement>(
            element,
            '#section'
          ).classList.contains('editingRef')
        );
        element.editing = false;
        await element.updateComplete;
        assert.isFalse(element.editingRef);
        assert.equal(element.section.id, 'refs/for/bar');
      });

      test('handleValueChange', async () => {
        // For an existing section.
        const modifiedHandler = sinon.stub();
        element.section = {
          id: 'refs/for/bar' as GitRef,
          value: {permissions: {}},
        };
        await element.updateComplete;
        assert.notOk(element.section.value.updatedId);
        element.section.id = 'refs/for/baz' as GitRef;
        await element.updateComplete;
        element.addEventListener('access-modified', modifiedHandler);
        assert.isNotOk(element.section.value.modified);
        element.handleValueChange();
        assert.equal(element.section.value.updatedId, 'refs/for/baz');
        assert.isTrue(element.section.value.modified);
        assert.equal(modifiedHandler.callCount, 1);
        element.section.id = 'refs/for/bar' as GitRef;
        await element.updateComplete;
        element.handleValueChange();
        assert.isFalse(element.section.value.modified);
        assert.equal(modifiedHandler.callCount, 2);

        // For a new section.
        element.section.value.added = true;
        await element.updateComplete;
        element.handleValueChange();
        assert.isFalse(element.section.value.modified);
        assert.equal(modifiedHandler.callCount, 2);
        element.section.id = 'refs/for/bar' as GitRef;
        await element.updateComplete;
        element.handleValueChange();
        assert.isFalse(element.section.value.modified);
        assert.equal(modifiedHandler.callCount, 2);
      });

      test('remove section', async () => {
        element.editing = true;
        element.canUpload = true;
        element.ownerOf = [];
        await element.updateComplete;
        assert.isFalse(element.deleted);
        assert.isNotOk(element.section!.value.deleted);
        queryAndAssert<GrButton>(element, '#deleteBtn').click();
        await element.updateComplete;
        assert.isTrue(element.deleted);
        assert.isTrue(element.section!.value.deleted);
        assert.isTrue(
          queryAndAssert<HTMLFieldSetElement>(
            element,
            '#section'
          ).classList.contains('deleted')
        );
        assert.isTrue(element.section!.value.deleted);

        queryAndAssert<GrButton>(element, '#undoRemoveBtn').click();
        await element.updateComplete;
        assert.isFalse(element.deleted);
        assert.isNotOk(element.section!.value.deleted);

        queryAndAssert<GrButton>(element, '#deleteBtn').click();
        await element.updateComplete;
        assert.isTrue(element.deleted);
        assert.isTrue(element.section!.value.deleted);
        element.editing = false;
        await element.updateComplete;
        assert.isFalse(element.deleted);
        assert.isNotOk(element.section!.value.deleted);
      });

      test('removing an added permission', async () => {
        element.editing = true;
        await element.updateComplete;
        assert.equal(element.permissions!.length, 1);
        element.shadowRoot!.querySelector('gr-permission')!.dispatchEvent(
          new CustomEvent('added-permission-removed', {
            composed: true,
            bubbles: true,
          })
        );
        await element.updateComplete;
        assert.equal(element.permissions!.length, 0);
      });

      test('remove an added section', async () => {
        const removeStub = sinon.stub();
        element.addEventListener('added-section-removed', removeStub);
        element.editing = true;
        element.section!.value.added = true;
        await element.updateComplete;
        queryAndAssert<GrButton>(element, '#deleteBtn').click();
        await element.updateComplete;
        assert.isTrue(removeStub.called);
      });
    });
  });
});
