package com.airlock.iam.oauth2.application.configuration.resource;

import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.license.PluginLicenseRestriction;
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.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.PluginListPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Plugin(description = "An OAuth 2.0 resource configuration.", displayName = "OAuth 2.0 Resource")
@PluginLicenseRestriction(moduleAffiliation = "OAuthServer")
public class OAuth2ResourceConfig {
   @StringProperty(
      description = "<p>The name of this resource.</p>\n<p>This value will be part of the URL:\n<tt>/&lt;loginapp-uri&gt;/rest/oauth2/authorization-servers/<i>&lt;as-identifier&gt;</i>/resources/<i>&lt;resource-name&gt;</i></tt></p>",
      optional = false,
      exampleValues = {"username", "surname"},
      validationRegex = "[a-zA-Z0-9._-]+"
   )
   private String resourceName;
   static final String RESOURCE_NAME_PROPERTY = "resourceName";
   @PluginListProperty(
      description = "<p>A list of OAuth 2.0 resource content providers.</p>\n<p>Each provider may contribute any number of claims to the final result of this resource.</p>\n<p>Multiple claims with the same name can be configured if each has a claim condition which ensures that only\none of them will be included at runtime.</p>\n",
      optional = false,
      minElements = 1,
      displayName = "Resource Content Providers"
   )
   private List<OAuth2ResourceProviderConfig> resourceProviders;
   private static final String RESOURCE_PROVIDERS_PROPERTY = "resourceProviders";
   @StringListProperty(
      description = "<p>Scopes required to access this resource.</p>\n<p>If scope values are added to this list, the access token has to cover at least all configured scopes to\nbe able to access this resource.</p>\n<p>If this list is left empty, there are no required scopes to access this resource, meaning it is\naccessible to anyone presenting a valid access token, regardless of the presented scopes. Note however, that\nthe individual resource providers may have a configured condition requiring that certain scopes be present\nto include the corresponding data element in the response.</p>",
      exampleElements = {"authenticated", "admin"},
      optional = true
   )
   private List<String> requiredScopes;

   @ConfigurationValidator
   public static void validateUniqueIds(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      Set<String> ids = new HashSet<>();
      Set<String> idsWithCondition = new HashSet<>();

      for (PluginConfiguration res : PluginListPropertyValueReader.effectivePluginListPropertyValue(config, "resourceProviders", env, context)) {
         if (res.isOfPluginClass(AbstractOAuth2SingleResourceProviderConfig.class)) {
            String id = StringPropertyValueReader.effectiveStringConfigValue(res, "identifier", env, context);
            if (!isConditional(res, env, context) || ids.contains(id)) {
               if (id != null && (!ids.add(id) || idsWithCondition.contains(id))) {
                  throw new PropertyConstraintViolationException("identifier", res, env, context, "Identifier is not unique inside this resource mapping.");
               }
            } else {
               idsWithCondition.add(id);
            }
         }
      }
   }

   private static boolean isConditional(PluginConfiguration res, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyTypeMismatchException {
      return PluginPropertyValueReader.effectivePluginPropertyValue(res, "condition", env, context) != null;
   }

   public String getResourceName() {
      return this.resourceName;
   }

   public List<OAuth2ResourceProviderConfig> getResourceProviders() {
      return this.resourceProviders;
   }

   public List<String> getRequiredScopes() {
      return this.requiredScopes;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2ResourceConfig(String resourceName, List<OAuth2ResourceProviderConfig> resourceProviders, List<String> requiredScopes) {
      this.resourceName = resourceName;
      this.resourceProviders = resourceProviders;
      this.requiredScopes = requiredScopes;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2ResourceConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String resourceName;
      @SuppressFBWarnings(justification = "generated code")
      private List<OAuth2ResourceProviderConfig> resourceProviders;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> requiredScopes;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ResourceConfig.OAuth2ResourceConfigBuilder resourceName(String resourceName) {
         this.resourceName = resourceName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ResourceConfig.OAuth2ResourceConfigBuilder resourceProviders(List<OAuth2ResourceProviderConfig> resourceProviders) {
         this.resourceProviders = resourceProviders;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ResourceConfig.OAuth2ResourceConfigBuilder requiredScopes(List<String> requiredScopes) {
         this.requiredScopes = requiredScopes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ResourceConfig build() {
         return new OAuth2ResourceConfig(this.resourceName, this.resourceProviders, this.requiredScopes);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2ResourceConfig.OAuth2ResourceConfigBuilder(resourceName="
            + this.resourceName
            + ", resourceProviders="
            + this.resourceProviders
            + ", requiredScopes="
            + this.requiredScopes
            + ")";
      }
   }
}
