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

import com.airlock.iam.common.application.configuration.event.LoginappUserSubscribedEventConfig;
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.RegexListProperty;
import com.airlock.iam.plugin.api.annotation.property.RegexProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import lombok.NonNull;

@Plugin(
   description = "Event that is published in case an OAuth 2.0 Consent is granted or denied.\nThis can happen in the following two scenarios:\n<ol>\n<li><strong>Login:</strong> Initiated by an \"OAuth 2.0 Consent Step\", the user can either (depending on the configuration) explicitly grant consent directly in the\nflow (Local Consent), or grant consent on another system by being redirected (Remote Consent), or grant consent implicitly without interaction (No Consent).\nThe event is published for any of these consent types. The event is not published in case the user denies access, leading to a flow termination or in case the scopes have\nbeen granted/declined previously (only if persistent consents are configured).</li>\n<li><strong>Self-Service:</strong> In case the scopes accepted and denied during login are persisted on the IAM (depends on the configuration),\nusers can accept or deny them later in a protected self-service flow. The event is not published, in case the user deletes a consent.</li>\n</ol>\nThe following information can be used under the corresponding key when consuming the event:\n<ul>\n<li><code>event.data.oauth2.authorizationServerId</code> Identifier of the OAuth 2.0 Authorization Server which has been involved when granting the consent.</li>\n<li><code>event.data.oauth2.clientId</code> Identifier of the OAuth 2.0 Client which has been involved when granting the consent.</li>\n<li><code>event.data.oauth2.acceptedScopes</code> The set of scopes which have been accepted by the user. May be empty.</li>\n<li><code>event.data.oauth2.deniedScopes</code> The set of scopes which have been denied by the user. May be empty.</li>\n<li><code>event.data.oauth2.alwaysGrantedScopes</code> The set of scopes which will always be granted. May be empty.\nThese scopes never require explicit consent of the user. They can be derived from the configuration of the OAuth 2.0\nClient and Authorization Server and are added here for convenience.\nAdding or removing always granted scopes from the configuration does not trigger events.</li>\n</ul>\n<p>In order to subscribe only to a subset of the published events of this type, multiple filtering properties can be configured.\n<strong>Note:</strong>If several of these properties are configured, all conditions must be met.</p>\n<p><strong>Use Case 1</strong>: You want to handle all events of this type.</br>\nUse the default configuration.\n</p>\n<p><strong>Use Case 2</strong>: You want to handle events of this type, which have at least both of the accepted scopes\n<code>email_verified</code> and <code>profile</code></br>\nRequired values for Accepted Scopes Patterns: <code>email_verified</code> and <code>profile</code></br>\nHandle Empty Accepted Scopes: <code>false</code></p>\n<p><strong>Use Case 3</strong>: You only want to handle events of this type which have no denied scopes.</br>\nRequired values for Denied Scopes Patterns: <code>^$</code></br>\nHandle Empty Denied Scopes: <code>true</code></p>\n<p><strong>Use Case 4</strong>: You only want to handle events of this type if the consent has been given remotely.</br>\nThe consent type (Local Consent, Remote Consent, No Consent) is determined by the configuration of the OAuth 2.0\nAuthorization Server and the OAuth 2.0 Client. Therefore, you can use their IDs for filtering.\n<p><strong>Use Case 5</strong>: You only want to handle events of this type if the consent has been given during\nlogin and not as a self-service.</br>\nUse a \"Filtered Flow Event\" and filter for the corresponding flow type.\n</p>\n",
   displayName = "OAuth 2.0 User Consent Processed"
)
@PluginLicenseRestriction(moduleAffiliation = "OAuthServer")
public final class OAuth2UserConsentSubscribedEventConfig implements LoginappUserSubscribedEventConfig {
   @RegexProperty(
      description = "When configured, the event is only handled by the subscriber, if the OAuth 2.0 Authorization Server matches this pattern.<p>Without a configured pattern (default), the value of the OAuth 2.0 Authorization Server is not considered for filtering the event.</p>",
      optional = true,
      exampleValues = {"my-auth-server", "my-other-auth-server.*", ".*my-other-auth-server"},
      displayName = "Authorization Server ID Pattern"
   )
   private Pattern authorizationServerIdPattern;
   @RegexProperty(
      description = "When configured, the event is only handled by the subscriber, if the OAuth 2.0 Client ID matches this pattern.<p>Without a configured pattern (default), the event is handled independent of the OAuth 2.0 Client ID.</p>",
      optional = true,
      exampleValues = {"my-client", "my-client-.*", ".*frontend-app"},
      displayName = "Client ID Pattern"
   )
   private Pattern clientIdPattern;
   @RegexListProperty(
      description = "When configured, the event is only handled by the subscriber, if all configured patterns match at least one accepted OAuth 2.0 scope. <p>Without a configured pattern (default), the event is handled independent of the accepted scopes.</p><p>If there are no accepted scopes, this property has no impact on the event filtering.</p>",
      optional = true,
      exampleElements = {"profile", "email.*"}
   )
   private List<Pattern> acceptedScopesPatterns;
   @BooleanProperty(
      description = "When set to false, the event is not handled in case there are no accepted scopes. When set to true (default), this property has no impact on the event filtering.",
      optional = true,
      defaultValue = "true"
   )
   private boolean handleEmptyAcceptedScopes;
   @RegexListProperty(
      description = "When configured, the event is only handled by the subscriber, if all configured patterns match at least one denied OAuth 2.0 scope. <p>Without a configured pattern (default), the event is handled independent of the denied scopes.</p><p>If there are no denied scopes, this property has no impact on the event filtering.</p>",
      optional = true,
      exampleElements = {"profile", "email.*"}
   )
   private List<Pattern> deniedScopesPatterns;
   @BooleanProperty(
      description = "When set to false, the event is not handled in case there are no denied scopes. When set to true (default), this property has no impact on the event filtering.",
      optional = true,
      defaultValue = "true"
   )
   private boolean handleEmptyDeniedScopes;
   @RegexListProperty(
      description = "When configured, the event is only handled by the subscriber, if all configured patterns match at least one always granted OAuth 2.0 scope. <p>Without a configured pattern (default), the event is handled independent of the always granted scopes.</p><p>If there are no always granted scopes, this property has no impact on the event filtering.</p>",
      optional = true,
      exampleElements = {"profile", "email.*"}
   )
   private List<Pattern> alwaysGrantedScopesPatterns;
   @BooleanProperty(
      description = "When set to false, the event is not handled in case there are no always granted scopes. When set to true (default), this property has no impact on the event filtering.",
      optional = true,
      defaultValue = "true"
   )
   private boolean handleEmptyAlwaysGrantedScopes;

   public Optional<Pattern> getAuthorizationServerIdPattern() {
      return Optional.ofNullable(this.authorizationServerIdPattern);
   }

   public Optional<Pattern> getClientIdPattern() {
      return Optional.ofNullable(this.clientIdPattern);
   }

   @NonNull
   public List<Pattern> getAcceptedScopesPatterns() {
      return this.acceptedScopesPatterns;
   }

   @NonNull
   public List<Pattern> getDeniedScopesPatterns() {
      return this.deniedScopesPatterns;
   }

   @NonNull
   public List<Pattern> getAlwaysGrantedScopesPatterns() {
      return this.alwaysGrantedScopesPatterns;
   }

   public boolean shouldHandleEmptyAcceptedScopes() {
      return this.handleEmptyAcceptedScopes;
   }

   public boolean shouldHandleEmptyDeniedScopes() {
      return this.handleEmptyDeniedScopes;
   }

   public boolean shouldHandleEmptyAlwaysGrantedScopes() {
      return this.handleEmptyAlwaysGrantedScopes;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2UserConsentSubscribedEventConfig(
      Pattern authorizationServerIdPattern,
      Pattern clientIdPattern,
      List<Pattern> acceptedScopesPatterns,
      boolean handleEmptyAcceptedScopes,
      List<Pattern> deniedScopesPatterns,
      boolean handleEmptyDeniedScopes,
      List<Pattern> alwaysGrantedScopesPatterns,
      boolean handleEmptyAlwaysGrantedScopes
   ) {
      this.authorizationServerIdPattern = authorizationServerIdPattern;
      this.clientIdPattern = clientIdPattern;
      this.acceptedScopesPatterns = acceptedScopesPatterns;
      this.handleEmptyAcceptedScopes = handleEmptyAcceptedScopes;
      this.deniedScopesPatterns = deniedScopesPatterns;
      this.handleEmptyDeniedScopes = handleEmptyDeniedScopes;
      this.alwaysGrantedScopesPatterns = alwaysGrantedScopesPatterns;
      this.handleEmptyAlwaysGrantedScopes = handleEmptyAlwaysGrantedScopes;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2UserConsentSubscribedEventConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Pattern authorizationServerIdPattern;
      @SuppressFBWarnings(justification = "generated code")
      private Pattern clientIdPattern;
      @SuppressFBWarnings(justification = "generated code")
      private List<Pattern> acceptedScopesPatterns;
      @SuppressFBWarnings(justification = "generated code")
      private boolean handleEmptyAcceptedScopes;
      @SuppressFBWarnings(justification = "generated code")
      private List<Pattern> deniedScopesPatterns;
      @SuppressFBWarnings(justification = "generated code")
      private boolean handleEmptyDeniedScopes;
      @SuppressFBWarnings(justification = "generated code")
      private List<Pattern> alwaysGrantedScopesPatterns;
      @SuppressFBWarnings(justification = "generated code")
      private boolean handleEmptyAlwaysGrantedScopes;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2UserConsentSubscribedEventConfig.OAuth2UserConsentSubscribedEventConfigBuilder authorizationServerIdPattern(
         Pattern authorizationServerIdPattern
      ) {
         this.authorizationServerIdPattern = authorizationServerIdPattern;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2UserConsentSubscribedEventConfig.OAuth2UserConsentSubscribedEventConfigBuilder clientIdPattern(Pattern clientIdPattern) {
         this.clientIdPattern = clientIdPattern;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2UserConsentSubscribedEventConfig.OAuth2UserConsentSubscribedEventConfigBuilder acceptedScopesPatterns(List<Pattern> acceptedScopesPatterns) {
         this.acceptedScopesPatterns = acceptedScopesPatterns;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2UserConsentSubscribedEventConfig.OAuth2UserConsentSubscribedEventConfigBuilder handleEmptyAcceptedScopes(boolean handleEmptyAcceptedScopes) {
         this.handleEmptyAcceptedScopes = handleEmptyAcceptedScopes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2UserConsentSubscribedEventConfig.OAuth2UserConsentSubscribedEventConfigBuilder deniedScopesPatterns(List<Pattern> deniedScopesPatterns) {
         this.deniedScopesPatterns = deniedScopesPatterns;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2UserConsentSubscribedEventConfig.OAuth2UserConsentSubscribedEventConfigBuilder handleEmptyDeniedScopes(boolean handleEmptyDeniedScopes) {
         this.handleEmptyDeniedScopes = handleEmptyDeniedScopes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2UserConsentSubscribedEventConfig.OAuth2UserConsentSubscribedEventConfigBuilder alwaysGrantedScopesPatterns(
         List<Pattern> alwaysGrantedScopesPatterns
      ) {
         this.alwaysGrantedScopesPatterns = alwaysGrantedScopesPatterns;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2UserConsentSubscribedEventConfig.OAuth2UserConsentSubscribedEventConfigBuilder handleEmptyAlwaysGrantedScopes(
         boolean handleEmptyAlwaysGrantedScopes
      ) {
         this.handleEmptyAlwaysGrantedScopes = handleEmptyAlwaysGrantedScopes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2UserConsentSubscribedEventConfig build() {
         return new OAuth2UserConsentSubscribedEventConfig(
            this.authorizationServerIdPattern,
            this.clientIdPattern,
            this.acceptedScopesPatterns,
            this.handleEmptyAcceptedScopes,
            this.deniedScopesPatterns,
            this.handleEmptyDeniedScopes,
            this.alwaysGrantedScopesPatterns,
            this.handleEmptyAlwaysGrantedScopes
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2UserConsentSubscribedEventConfig.OAuth2UserConsentSubscribedEventConfigBuilder(authorizationServerIdPattern="
            + this.authorizationServerIdPattern
            + ", clientIdPattern="
            + this.clientIdPattern
            + ", acceptedScopesPatterns="
            + this.acceptedScopesPatterns
            + ", handleEmptyAcceptedScopes="
            + this.handleEmptyAcceptedScopes
            + ", deniedScopesPatterns="
            + this.deniedScopesPatterns
            + ", handleEmptyDeniedScopes="
            + this.handleEmptyDeniedScopes
            + ", alwaysGrantedScopesPatterns="
            + this.alwaysGrantedScopesPatterns
            + ", handleEmptyAlwaysGrantedScopes="
            + this.handleEmptyAlwaysGrantedScopes
            + ")";
      }
   }
}
