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

import com.airlock.iam.oauth2.api.application.configuration.clientauthentication.OAuth2ClientAuthenticationConfig;
import com.airlock.iam.oauth2.application.configuration.clientauthentication.OAuth2ClientMTLSAuthenticationConfig;
import com.airlock.iam.oauth2.application.configuration.clientauthentication.OAuth2ClientSecretAuthenticationConfig;
import com.airlock.iam.oauth2.application.configuration.scope.OAuth2ScopeMatcherConfig;
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.BooleanProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
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.BooleanPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;

@Plugin(
   description = "<p>Configuration of the token endpoint for OAuth 2.0 or OpenID Connect.</p><p>The endpoint will be available under <tt>/&lt;loginapp-uri&gt;/rest/oauth2/authorization-servers/<i>&lt;as-identifier&gt;</i>/token</tt></p>",
   displayName = "OAuth 2.0 Token Endpoint"
)
@PluginLicenseRestriction(moduleAffiliation = "OAuthServer")
public final class OAuth2TokenEndpointConfig {
   @PluginProperty(
      description = "Specifies if and how requests to the token endpoint and to the 'Pushed Authorization Requests' (PAR) endpoint are authenticated.",
      optional = true,
      defaultValue = OAuth2ClientSecretAuthenticationConfig.class
   )
   private OAuth2ClientAuthenticationConfig clientAuthentication;
   public static final String CLIENT_AUTHENTICATION_PROPERTY = "clientAuthentication";
   @PluginListProperty(
      description = "List of matchers that defines the set of scopes not to be refreshed in a refresh token grant. If a scope matches against any entry in the list, it will be removed from the session and not be part of newly acquired tokens.",
      optional = true
   )
   private List<OAuth2ScopeMatcherConfig> scopesToRemoveOnRefresh;
   @BooleanProperty(
      description = "If enabled, Certificate-Bound Access Tokens (see <a href=\"https://datatracker.ietf.org/doc/html/rfc8705\" target=\"_blank\">RFC8705</a>) are issued.<p>Issued Access Tokens will be bound to the client certificate that was used for client authentication for the Token Endpoint Authentication. This will enable the \"cnf\" claim to be included in the Token Introspection Endpoint result as well as in the JWT Access Token (if enabled). All consumers must utilise the claim to verify the client certificate being used with the Access Token.</p><p>IAM consumes such Access Tokens (Token Revocation Endpoint, Session Management Endpoint, Resource Endpoints, UserInfo Endpoint and One-Shot Authenticators) and will therefore automatically verify the mTLS client certificate, if an Certificate-Bound Access Token is used for authorization.</p><p><b>Note:</b> Currently this setting is restricted to client authentications using mTLS.</p>",
      optional = true,
      displayName = "Issue Certificate-Bound Access Tokens",
      defaultValue = "false"
   )
   private boolean bindAccessTokens;
   private static final String BIND_ACCESS_TOKENS_FIELD = "bindAccessTokens";

   @ConfigurationValidator
   public static void validateCertificateBinding(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      boolean bindCertificates = BooleanPropertyValueReader.effectiveBooleanPropertyValue(config, "bindAccessTokens", env, context);
      PluginConfiguration clientAuthenticationConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "clientAuthentication", env, context);
      if (bindCertificates && !clientAuthenticationConfig.isOfPluginClass(OAuth2ClientMTLSAuthenticationConfig.class)) {
         throw new PropertyConstraintViolationException(
            "bindAccessTokens",
            config,
            env,
            context,
            "Binding Access Tokens to certificates is currently only available in combination with mTLS client authentication."
         );
      }
   }

   public List<OAuth2ScopeMatcherConfig> getScopesToRemoveOnRefresh() {
      return List.copyOf(this.scopesToRemoveOnRefresh);
   }

   public OAuth2ClientAuthenticationConfig getClientAuthentication() {
      return this.clientAuthentication;
   }

   public boolean isBindAccessTokens() {
      return this.bindAccessTokens;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2TokenEndpointConfig(
      OAuth2ClientAuthenticationConfig clientAuthentication, List<OAuth2ScopeMatcherConfig> scopesToRemoveOnRefresh, boolean bindAccessTokens
   ) {
      this.clientAuthentication = clientAuthentication;
      this.scopesToRemoveOnRefresh = scopesToRemoveOnRefresh;
      this.bindAccessTokens = bindAccessTokens;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2TokenEndpointConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2ClientAuthenticationConfig clientAuthentication;
      @SuppressFBWarnings(justification = "generated code")
      private List<OAuth2ScopeMatcherConfig> scopesToRemoveOnRefresh;
      @SuppressFBWarnings(justification = "generated code")
      private boolean bindAccessTokens;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenEndpointConfig.OAuth2TokenEndpointConfigBuilder clientAuthentication(OAuth2ClientAuthenticationConfig clientAuthentication) {
         this.clientAuthentication = clientAuthentication;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenEndpointConfig.OAuth2TokenEndpointConfigBuilder scopesToRemoveOnRefresh(List<OAuth2ScopeMatcherConfig> scopesToRemoveOnRefresh) {
         this.scopesToRemoveOnRefresh = scopesToRemoveOnRefresh;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenEndpointConfig.OAuth2TokenEndpointConfigBuilder bindAccessTokens(boolean bindAccessTokens) {
         this.bindAccessTokens = bindAccessTokens;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenEndpointConfig build() {
         return new OAuth2TokenEndpointConfig(this.clientAuthentication, this.scopesToRemoveOnRefresh, this.bindAccessTokens);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2TokenEndpointConfig.OAuth2TokenEndpointConfigBuilder(clientAuthentication="
            + this.clientAuthentication
            + ", scopesToRemoveOnRefresh="
            + this.scopesToRemoveOnRefresh
            + ", bindAccessTokens="
            + this.bindAccessTokens
            + ")";
      }
   }
}
