package com.airlock.iam.authentication.application.configuration.risk.accesspolicy;

import com.airlock.iam.authentication.application.configuration.risk.RiskTagPlugin;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
import com.airlock.iam.plugin.api.annotation.config.Groups;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.StringListProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.valuereader.PluginListPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;

@Plugin(description = "An access policy rule deriving new roles from existing roles and Risk Tags.", displayName = "Simple Risk-based Role Derivation")
@Groups(groupSequence = {"Preconditions", "Target Role"}, openGroups = {"Preconditions", "Target Role"})
public class SimpleRiskBasedRoleDerivationConfig implements RoleDerivationRuleConfig {
   static final String RULE_PRECONDTIONS = "Preconditions";
   static final String TARGET_ROLE = "Target Role";
   @StringListProperty(
      description = "If defined, this rule only matches if the user has <em>at least one</em> of the specified roles.",
      optional = true,
      exampleElements = "weak"
   )
   @GroupProperty(groupIdentifier = "Preconditions")
   private List<String> requiredRoles;
   @PluginListProperty(description = "If defined, this rule only matches if the user has <em>all</em> of the specified Risk Tags.", optional = true)
   @GroupProperty(groupIdentifier = "Preconditions")
   private List<RiskTagPlugin> mandatoryRiskTags;
   private static final String REQUIRED_RISK_TAGS_FIELD = "mandatoryRiskTags";
   @PluginListProperty(description = "A list of Risk Tags that <b>must not</b> be present.", optional = true)
   @GroupProperty(groupIdentifier = "Preconditions")
   private List<RiskTagPlugin> excludingRiskTags;
   private static final String EXCLUDING_RISK_TAGS_FIELD = "excludingRiskTags";
   @StringProperty(description = "The resulting role if all required roles and risk tags can be satisfied.", optional = false, exampleValues = "strong")
   @GroupProperty(groupIdentifier = "Target Role")
   private String targetRole;

   @ConfigurationValidator
   public static void validateRiskTagsConstraints(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      List<PluginConfiguration> requiredRiskTagsConfig = PluginListPropertyValueReader.effectivePluginListPropertyValue(
         config, "mandatoryRiskTags", env, context
      );
      Set<String> requiredriskTags = getRiskTagNames(requiredRiskTagsConfig, env, context);
      List<PluginConfiguration> excludingRiskTagsConfig = PluginListPropertyValueReader.effectivePluginListPropertyValue(
         config, "excludingRiskTags", env, context
      );
      Set<String> excludingRiskTags = getRiskTagNames(excludingRiskTagsConfig, env, context);
      SetView<String> intersection = Sets.intersection(requiredriskTags, excludingRiskTags);
      if (!intersection.isEmpty()) {
         throw new PropertyConstraintViolationException(
            "mandatoryRiskTags", config, env, context, "The following risk tag(s) are defined in both required and excluding lists: " + intersection
         );
      }
   }

   private static Set<String> getRiskTagNames(List<PluginConfiguration> riskTagPlugins, ConfigurationEnvironment env, ConfigurationContext context) throws ConfigurationException {
      Set<String> names = new HashSet<>();

      for (PluginConfiguration plugin : riskTagPlugins) {
         String riskTagName = StringUtils.trimToNull(StringPropertyValueReader.effectiveStringConfigValue(plugin, "name", env, context));
         if (riskTagName != null && !names.add(riskTagName)) {
            throw new PropertyConstraintViolationException("name", plugin, env, context, "Duplicate Risk Tag name: " + riskTagName);
         }
      }

      return names;
   }

   public List<String> getRequiredRoles() {
      return this.requiredRoles;
   }

   public List<RiskTagPlugin> getMandatoryRiskTags() {
      return this.mandatoryRiskTags;
   }

   public List<RiskTagPlugin> getExcludingRiskTags() {
      return this.excludingRiskTags;
   }

   public String getTargetRole() {
      return this.targetRole;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public SimpleRiskBasedRoleDerivationConfig(
      List<String> requiredRoles, List<RiskTagPlugin> mandatoryRiskTags, List<RiskTagPlugin> excludingRiskTags, String targetRole
   ) {
      this.requiredRoles = requiredRoles;
      this.mandatoryRiskTags = mandatoryRiskTags;
      this.excludingRiskTags = excludingRiskTags;
      this.targetRole = targetRole;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class SimpleRiskBasedRoleDerivationConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private List<String> requiredRoles;
      @SuppressFBWarnings(justification = "generated code")
      private List<RiskTagPlugin> mandatoryRiskTags;
      @SuppressFBWarnings(justification = "generated code")
      private List<RiskTagPlugin> excludingRiskTags;
      @SuppressFBWarnings(justification = "generated code")
      private String targetRole;

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

      @SuppressFBWarnings(justification = "generated code")
      public SimpleRiskBasedRoleDerivationConfig.SimpleRiskBasedRoleDerivationConfigBuilder requiredRoles(List<String> requiredRoles) {
         this.requiredRoles = requiredRoles;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SimpleRiskBasedRoleDerivationConfig.SimpleRiskBasedRoleDerivationConfigBuilder mandatoryRiskTags(List<RiskTagPlugin> mandatoryRiskTags) {
         this.mandatoryRiskTags = mandatoryRiskTags;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SimpleRiskBasedRoleDerivationConfig.SimpleRiskBasedRoleDerivationConfigBuilder excludingRiskTags(List<RiskTagPlugin> excludingRiskTags) {
         this.excludingRiskTags = excludingRiskTags;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SimpleRiskBasedRoleDerivationConfig.SimpleRiskBasedRoleDerivationConfigBuilder targetRole(String targetRole) {
         this.targetRole = targetRole;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SimpleRiskBasedRoleDerivationConfig build() {
         return new SimpleRiskBasedRoleDerivationConfig(this.requiredRoles, this.mandatoryRiskTags, this.excludingRiskTags, this.targetRole);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "SimpleRiskBasedRoleDerivationConfig.SimpleRiskBasedRoleDerivationConfigBuilder(requiredRoles="
            + this.requiredRoles
            + ", mandatoryRiskTags="
            + this.mandatoryRiskTags
            + ", excludingRiskTags="
            + this.excludingRiskTags
            + ", targetRole="
            + this.targetRole
            + ")";
      }
   }
}
