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

import com.airlock.iam.oauth2.application.configuration.scopepolicy.ScopePolicyConfig;
import com.airlock.iam.oauth2.application.configuration.signature.JwtAccessTokenPrivateKeySignature;
import com.airlock.iam.oauth2.application.configuration.token.OAuth2TokenEndpointGrantedScopeProcessorConfig;
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.EnumProperty;
import com.airlock.iam.plugin.api.annotation.property.IntegerProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringListProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.Optional;

@Plugin(
   description = "<p>Configures an OAuth 2.0 Client Credentials Grant. Issued Access Token are self-contained JWTs.\nTherefore, tokens will not be persisted and cannot be revoked.</p>\n<p>The Client Credentials Grant uses the following endpoint:</p>\n<ol>\n<li><code>/&lt;loginapp-uri&gt;/rest/oauth2/authorization-servers/<i>&lt;as-identifier&gt;</i>/token</code> - The Token Endpoint</li>\n</ol>",
   displayName = "OAuth 2.0 Client Credentials Grant"
)
@PluginLicenseRestriction(moduleAffiliation = "OAuthServer")
public class OAuth2ClientCredentialsGrantConfig {
   @IntegerProperty(
      description = "Time in seconds for which an Access Token is valid.\n<p><b>Security Warning:</b> a very long Access Token validity is not recommended.\nClients can simply fetch a new access token by calling the endpoint again.</p>\n",
      minValue = 1,
      defaultValue = 180,
      exampleValues = {180, 3600},
      displayName = "Access Token Validity [s]",
      optional = true
   )
   private int accessTokenValidity;
   @StringProperty(
      description = "The issuer claim (<code>iss</code>) to include in the Access Token. If left empty the claim will not be included.",
      optional = true,
      exampleValues = {"https://example.org/auth/rest/oauth2/authorization-servers/as-identifier", "custom-issuer"}
   )
   private String issuer;
   @StringListProperty(
      description = "<p>The audience claim (<code>aud</code>) to include in the Access Token. If left empty the claim will not be included.</p>\n<p>If there is one audience, the claim is written as a string, for multiple values as an array.</p>",
      optional = true,
      exampleElements = {"https://airlock.com/auth-login/", "https://host1:443/path"}
   )
   private List<String> audience;
   @EnumProperty(
      displayName = "Scope Policy",
      description = "<p>The scope policy defines how the requested scopes are validated and processed (before the scope processors are applied).</p><p>Depending on the selected policy, the following rules apply:</p><ul><li><code>Scopes Mandatory</code>: It is mandatory for the client to request at least one scope, otherwise the request is denied. <ul><li>For static clients for which 'Filter Requested Scopes' is enabled: the requested scopes are filtered against the client's allowed scopes and if the client has     no allowed scopes, this is treated as if the client has not requested any scopes at all.</li><li>For static clients for which 'Filter Requested Scopes' is disabled: the requested scopes are not filtered (i.e. all scopes are allowed to be requested).</li><li>For persisted clients, the allowed scopes to request are stored per client and it can be configured there what the effect of an empty list of allowed scopes is.</li></ul></li><li><code>Empty Scopes Allowed</code>: It is optional for the client to request scopes.<br>If scopes are requested:<br><ul><li>For static clients for which 'Filter Requested Scopes' is enabled: the requested scopes are filtered against the client's allowed scopes and if the client has     no allowed scopes, this is treated as if the client has not requested any scopes at all.</li><li>For static clients for which 'Filter Requested Scopes' is disabled: the requested scopes are not filtered (i.e. all scopes are allowed to be requested).</li><li>For persisted clients, the allowed scopes to request are stored per client and it can be configured there what the effect of an empty list of allowed scopes is.</li></ul></li><li><code>Always Overwrite Scopes</code>: The scopes requested by the client are ignored and replaced by the default scopes of the client. If the client has no default scopes, this is treated as if the client has not requested any scopes at all.<br>With this policy, the 'Filter Requested Scopes' flag of static clients is ignored.</li><li><code>Empty Scopes Overwritten</code>: When the client does not request any scopes, the request is treated as if the default scopes of this client were requested.<br>If scopes are requested:<br><ul><li>For static clients for which 'Filter Requested Scopes' is enabled: the requested scopes are filtered against the client's allowed scopes and if the client has     no allowed scopes, this is treated as if the client has not requested any scopes at all.</li><li>For static clients for which 'Filter Requested Scopes' is disabled: the requested scopes are not filtered (i.e. all scopes are allowed to be requested).</li><li>For persisted clients, the allowed scopes to request are stored per client and it can be configured there what the effect of an empty list of allowed scopes is.</li></ul></li></ul>",
      optional = true,
      defaultValue = "EMPTY_SCOPES_ALLOWED"
   )
   private ScopePolicyConfig scopePolicy;
   private static final String SCOPE_POLICY_DISPLAY_NAME = "Scope Policy";
   @PluginListProperty(
      description = "<p>Allows to further restrict the granted scopes (after applying the scope policy) before issuing the tokens.</p><p>The processors will be applied in the configured order and only scopes allowed by all processors may be granted.</p><p>If not configured, all granted scopes are assigned to the access token.</p><p><b>Notice:</b> the scope processors are applied after the configured Scope Policy and thus have no influence on whether the requested scopes are allowed.</p>",
      optional = true
   )
   private List<OAuth2TokenEndpointGrantedScopeProcessorConfig> grantedScopeProcessors;
   @PluginProperty(description = "The signature of the Access Token.", optional = false)
   private JwtAccessTokenPrivateKeySignature signature;

   public int getAccessTokenValidity() {
      return this.accessTokenValidity;
   }

   public Optional<String> getIssuer() {
      return Optional.ofNullable(this.issuer);
   }

   public List<String> getAudience() {
      return this.audience;
   }

   public ScopePolicyConfig getScopePolicy() {
      return this.scopePolicy;
   }

   public List<OAuth2TokenEndpointGrantedScopeProcessorConfig> getGrantedScopeProcessors() {
      return this.grantedScopeProcessors;
   }

   public JwtAccessTokenPrivateKeySignature getSignature() {
      return this.signature;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2ClientCredentialsGrantConfig(
      int accessTokenValidity,
      String issuer,
      List<String> audience,
      ScopePolicyConfig scopePolicy,
      List<OAuth2TokenEndpointGrantedScopeProcessorConfig> grantedScopeProcessors,
      JwtAccessTokenPrivateKeySignature signature
   ) {
      this.accessTokenValidity = accessTokenValidity;
      this.issuer = issuer;
      this.audience = audience;
      this.scopePolicy = scopePolicy;
      this.grantedScopeProcessors = grantedScopeProcessors;
      this.signature = signature;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2ClientCredentialsGrantConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private int accessTokenValidity;
      @SuppressFBWarnings(justification = "generated code")
      private String issuer;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> audience;
      @SuppressFBWarnings(justification = "generated code")
      private ScopePolicyConfig scopePolicy;
      @SuppressFBWarnings(justification = "generated code")
      private List<OAuth2TokenEndpointGrantedScopeProcessorConfig> grantedScopeProcessors;
      @SuppressFBWarnings(justification = "generated code")
      private JwtAccessTokenPrivateKeySignature signature;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientCredentialsGrantConfig.OAuth2ClientCredentialsGrantConfigBuilder accessTokenValidity(int accessTokenValidity) {
         this.accessTokenValidity = accessTokenValidity;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientCredentialsGrantConfig.OAuth2ClientCredentialsGrantConfigBuilder issuer(String issuer) {
         this.issuer = issuer;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientCredentialsGrantConfig.OAuth2ClientCredentialsGrantConfigBuilder audience(List<String> audience) {
         this.audience = audience;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientCredentialsGrantConfig.OAuth2ClientCredentialsGrantConfigBuilder scopePolicy(ScopePolicyConfig scopePolicy) {
         this.scopePolicy = scopePolicy;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientCredentialsGrantConfig.OAuth2ClientCredentialsGrantConfigBuilder grantedScopeProcessors(
         List<OAuth2TokenEndpointGrantedScopeProcessorConfig> grantedScopeProcessors
      ) {
         this.grantedScopeProcessors = grantedScopeProcessors;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientCredentialsGrantConfig.OAuth2ClientCredentialsGrantConfigBuilder signature(JwtAccessTokenPrivateKeySignature signature) {
         this.signature = signature;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientCredentialsGrantConfig build() {
         return new OAuth2ClientCredentialsGrantConfig(
            this.accessTokenValidity, this.issuer, this.audience, this.scopePolicy, this.grantedScopeProcessors, this.signature
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2ClientCredentialsGrantConfig.OAuth2ClientCredentialsGrantConfigBuilder(accessTokenValidity="
            + this.accessTokenValidity
            + ", issuer="
            + this.issuer
            + ", audience="
            + this.audience
            + ", scopePolicy="
            + this.scopePolicy
            + ", grantedScopeProcessors="
            + this.grantedScopeProcessors
            + ", signature="
            + this.signature
            + ")";
      }
   }
}
