/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: MPL-2.0
 */

import { Ability } from 'ember-can';
import { inject as service } from '@ember/service';
import { computed, get } from '@ember/object';
import { equal, not } from '@ember/object/computed';
import classic from 'ember-classic-decorator';

@classic
export default class Abstract extends Ability {
  @service system;
  @service token;

  @not('token.aclEnabled') bypassAuthorization;
  @equal('token.selfToken.type', 'management') selfTokenIsManagement;

  // Pass in a namespace to `can` or `cannot` calls to override
  // https://github.com/minutebase/ember-can#additional-attributes
  namespace = 'default';

  get _namespace() {
    if (!this.namespace) return 'default';
    if (typeof this.namespace === 'string') return this.namespace;
    return get(this.namespace, 'name');
  }

  @computed('_namespace', 'token.selfTokenPolicies.[]')
  get rulesForNamespace() {
    let namespace = this._namespace;

    return (this.get('token.selfTokenPolicies') || [])
      .toArray()
      .reduce((rules, policy) => {
        let policyNamespaces = get(policy, 'rulesJSON.Namespaces') || [];

        let matchingNamespace = this._findMatchingNamespace(
          policyNamespaces,
          namespace
        );

        if (matchingNamespace) {
          rules.push(
            policyNamespaces.find(
              (namespace) => namespace.Name === matchingNamespace
            )
          );
        }

        return rules;
      }, []);
  }

  @computed('token.selfTokenPolicies.[]')
  get capabilitiesForAllNamespaces() {
    return (this.get('token.selfTokenPolicies') || [])
      .toArray()
      .reduce((allCapabilities, policy) => {
        (get(policy, 'rulesJSON.Namespaces') || []).forEach(
          ({ Capabilities }) => {
            allCapabilities = allCapabilities.concat(Capabilities);
          }
        );
        return allCapabilities;
      }, []);
  }

  namespaceIncludesCapability(capability) {
    return this.rulesForNamespace.some((rules) => {
      let capabilities = get(rules, 'Capabilities') || [];
      return capabilities.includes(capability);
    });
  }

  @computed('system.features.[]')
  get features() {
    return this.system.features;
  }

  featureIsPresent(featureName) {
    return this.features.includes(featureName);
  }

  // Chooses the closest namespace as described at the bottom here:
  // https://learn.hashicorp.com/tutorials/nomad/access-control-policies?in=nomad/access-control#namespace-rules
  _findMatchingNamespace(policyNamespaces, namespace) {
    let namespaceNames = policyNamespaces.mapBy('Name');

    if (namespaceNames.includes(namespace)) {
      return namespace;
    }

    let globNamespaceNames = namespaceNames.filter((namespaceName) =>
      namespaceName.includes('*')
    );

    let matchingNamespaceName = globNamespaceNames.reduce(
      (mostMatching, namespaceName) => {
        // Convert * wildcards to .* for regex matching
        let namespaceNameRegExp = new RegExp(
          namespaceName.replace(/\*/g, '.*')
        );
        let characterDifference = namespace.length - namespaceName.length;

        if (
          characterDifference < mostMatching.mostMatchingCharacterDifference &&
          namespace.match(namespaceNameRegExp)
        ) {
          return {
            mostMatchingNamespaceName: namespaceName,
            mostMatchingCharacterDifference: characterDifference,
          };
        } else {
          return mostMatching;
        }
      },
      {
        mostMatchingNamespaceName: null,
        mostMatchingCharacterDifference: Number.MAX_SAFE_INTEGER,
      }
    ).mostMatchingNamespaceName;

    if (matchingNamespaceName) {
      return matchingNamespaceName;
    } else if (namespaceNames.includes('default')) {
      return 'default';
    }
  }
}
