import { ACLConfiguration, ACLPolicy, ACLRule } from "../configuration/schema/AclConfiguration";
import { IAuthorizer } from "./IAuthorizer";
import { Winston } from "../../../types/Dependencies";
import { MultipleDomainMatcher } from "./MultipleDomainMatcher";
import { Level } from "./Level";

function MatchDomain(actualDomain: string) {
  return function (rule: ACLRule): boolean {
    return MultipleDomainMatcher.match(actualDomain, rule.domain);
  };
}

function MatchResource(actualResource: string) {
  return function (rule: ACLRule): boolean {
    // If resources key is not provided, the rule applies to all resources.
    if (!rule.resources) return true;

    for (let i = 0; i < rule.resources.length; ++i) {
      const regexp = new RegExp(rule.resources[i]);
      if (regexp.test(actualResource)) return true;
    }
    return false;
  };
}

export class Authorizer implements IAuthorizer {
  private logger: Winston;
  private readonly configuration: ACLConfiguration;

  constructor(configuration: ACLConfiguration, logger_: Winston) {
    this.logger = logger_;
    this.configuration = configuration;
  }

  private getMatchingUserRules(user: string, domain: string, resource: string): ACLRule[] {
    const userRules = this.configuration.users[user];
    if (!userRules) return [];
    return userRules.filter(MatchDomain(domain)).filter(MatchResource(resource));
  }

  private getMatchingGroupRules(groups: string[], domain: string, resource: string): ACLRule[] {
    const that = this;
    // There is no ordering between group rules. That is, when a user belongs to 2 groups, there is no
    // guarantee one set of rules has precedence on the other one.
    const groupRules = groups.reduce(function (rules: ACLRule[], group: string) {
      const groupRules = that.configuration.groups[group];
      if (groupRules) rules = rules.concat(groupRules);
      return rules;
    }, []);
    return groupRules.filter(MatchDomain(domain)).filter(MatchResource(resource));
  }

  private getMatchingAllRules(domain: string, resource: string): ACLRule[] {
    const rules = this.configuration.any;
    if (!rules) return [];
    return rules.filter(MatchDomain(domain)).filter(MatchResource(resource));
  }

  authorization(domain: string, resource: string, user: string, groups: string[]): Level {
    if (!this.configuration) return Level.BYPASS;

    const allRules = this.getMatchingAllRules(domain, resource);
    const groupRules = this.getMatchingGroupRules(groups, domain, resource);
    const userRules = this.getMatchingUserRules(user, domain, resource);
    const rules = allRules.concat(groupRules).concat(userRules).reverse();
    const policy = rules.map(r => r.policy).concat([this.configuration.default_policy])[0];

    if (policy == "bypass") {
      return Level.BYPASS;
    } else if (policy == "one_factor") {
      return Level.ONE_FACTOR;
    } else if (policy == "two_factor") {
      return Level.TWO_FACTOR;
    }
    return Level.DENY;
  }
}