package com.airlock.iam.common.application.configuration.authorization;

import com.airlock.iam.base.misc.collection.Pair;
import com.airlock.iam.common.api.domain.model.authentication.Authentee;
import com.airlock.iam.common.api.domain.model.role.Role;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.property.RegexProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.framework.application.service.builder.plugin.PluginPropertyBuilderInitializer;
import com.google.common.base.Splitter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.regex.Pattern;

@Plugin(description = "Defines how a user in a specific role can access a resource.", displayName = "Resource Access Rule")
public final class ResourceAccessControllerAccessRule {
   private static final Splitter LIST_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
   private final MethodAndPathMatcher matcher = new MethodAndPathMatcher();
   @RegexProperty(
      description = "A pattern for matching the path of a resource. The matched path starts with the slash ('/') that separates the context path from the resource path.",
      exampleValues = {"/.*", "/users/[^/]+/password/change"},
      optional = false
   )
   private Pattern pathPattern;
   @StringProperty(
      description = "A pattern for matching the methods of a HTTP request. Multiple methods are specified as a comma-separated list. Use an asterisk ('*') to match any method.",
      suggestedValues = {"GET", "GET,PUT,POST,PATCH,DELETE", "*"},
      optional = false
   )
   private String methodPattern;
   @StringProperty(
      optional = true,
      description = "Defines the set of required roles needed to access the resource. Multiple roles are specified as comma-separated list. At least one of the roles is needed to be granted to take the action. No value means not allowed for anyone. 'NO RESTRICTION' allows access to everyone.",
      suggestedValues = {
            "NO RESTRICTION",
            "useradmin",
            "tokenadmin",
            "helpdesk",
            "sysadmin",
            "superadmin",
            "useradmin,tokenadmin",
            "useradmin,helpdesk",
            "tokenadmin,helpdesk",
            "sysadmin,superadmin",
            "useradmin,tokenadmin,helpdesk,sysadmin,superadmin"
      }
   )
   private String roles;

   public static ResourceAccessControllerAccessRule allowing(Pattern pathPattern, String methodPattern, String roles) {
      return ((ResourceAccessControllerAccessRule.ResourceAccessControllerAccessRuleBuilder)PluginPropertyBuilderInitializer.builder(
            ResourceAccessControllerAccessRule.ResourceAccessControllerAccessRuleBuilder.class
         ))
         .pathPattern(pathPattern)
         .methodPattern(methodPattern)
         .roles(roles)
         .build();
   }

   public boolean isApplicableTo(String action) {
      return this.isApplicableTo(this.splitAction(action));
   }

   private Pair<String, String> splitAction(String action) {
      int splitIndex = action.indexOf(32);
      if (splitIndex > 0) {
         String method = action.substring(0, splitIndex);
         String path = action.substring(splitIndex + 1);
         return Pair.pair(method, path);
      } else {
         return null;
      }
   }

   private boolean isApplicableTo(Pair<String, String> methodPathPair) {
      return methodPathPair == null ? false : this.isApplicableToMethod(methodPathPair.getFirst()) && this.isApplicableToPath(methodPathPair.getSecond());
   }

   private boolean isApplicableToMethod(String method) {
      return this.matcher.methodMatches(this.methodPattern, method);
   }

   private boolean isApplicableToPath(String path) {
      return this.matcher.pathMatches(this.pathPattern, path);
   }

   public boolean allows(String action, Authentee subject) {
      Pair<String, String> methodPathPair = this.splitAction(action);
      if (this.isApplicableTo(methodPathPair)) {
         String roleString = this.roles != null ? this.roles : "";
         Iterable<String> roleNames = LIST_SPLITTER.split(roleString);

         for (String role : roleNames) {
            if (subject.hasRole(Role.parseRole(role))) {
               return true;
            }
         }

         if (roleNames.iterator().hasNext() && "NO RESTRICTION".equals(roleNames.iterator().next())) {
            ResourceAccessController.LOG.debug("Accepting " + action + " because it's configured with 'NO RESTRICTION'.");
            return true;
         }

         ResourceAccessController.LOG.debug("Denying " + action + " because user '" + subject.userIdentity().id() + "' has none of roles " + roleString);
      }

      return false;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static ResourceAccessControllerAccessRule.ResourceAccessControllerAccessRuleBuilder builder() {
      return new ResourceAccessControllerAccessRule.ResourceAccessControllerAccessRuleBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public String toString() {
      return "ResourceAccessControllerAccessRule(matcher="
         + this.matcher
         + ", pathPattern="
         + this.pathPattern
         + ", methodPattern="
         + this.methodPattern
         + ", roles="
         + this.roles
         + ")";
   }

   @SuppressFBWarnings(justification = "generated code")
   public ResourceAccessControllerAccessRule() {
   }

   @SuppressFBWarnings(justification = "generated code")
   public ResourceAccessControllerAccessRule(Pattern pathPattern, String methodPattern, String roles) {
      this.pathPattern = pathPattern;
      this.methodPattern = methodPattern;
      this.roles = roles;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class ResourceAccessControllerAccessRuleBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Pattern pathPattern;
      @SuppressFBWarnings(justification = "generated code")
      private String methodPattern;
      @SuppressFBWarnings(justification = "generated code")
      private String roles;

      @SuppressFBWarnings(justification = "generated code")
      ResourceAccessControllerAccessRuleBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public ResourceAccessControllerAccessRule.ResourceAccessControllerAccessRuleBuilder pathPattern(Pattern pathPattern) {
         this.pathPattern = pathPattern;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ResourceAccessControllerAccessRule.ResourceAccessControllerAccessRuleBuilder methodPattern(String methodPattern) {
         this.methodPattern = methodPattern;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ResourceAccessControllerAccessRule.ResourceAccessControllerAccessRuleBuilder roles(String roles) {
         this.roles = roles;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ResourceAccessControllerAccessRule build() {
         return new ResourceAccessControllerAccessRule(this.pathPattern, this.methodPattern, this.roles);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "ResourceAccessControllerAccessRule.ResourceAccessControllerAccessRuleBuilder(pathPattern="
            + this.pathPattern
            + ", methodPattern="
            + this.methodPattern
            + ", roles="
            + this.roles
            + ")";
      }
   }
}
