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

import com.airlock.iam.core.misc.util.httpclient.HttpClientConfig;
import com.airlock.iam.flow.api.application.configuration.redflag.RedFlagConfig;
import com.airlock.iam.oauth2.application.configuration.accountlinking.AccountLinkingSelfServiceConfig;
import com.airlock.iam.oauth2.application.configuration.accountregistration.AccountRegistrationConfig;
import com.airlock.iam.oauth2.application.configuration.client.OAuth2ClientResourceRequest;
import com.airlock.iam.oauth2.application.configuration.client.OAuth2RedirectUriConfig;
import com.airlock.iam.oauth2.application.configuration.client.OAuth2RequestBuilderConfig;
import com.airlock.iam.oauth2.application.configuration.clientauthentication.OAuth2BasicAuthClientSecretConfig;
import com.airlock.iam.oauth2.application.configuration.logging.OAuth2LoggingSettings;
import com.airlock.iam.oauth2.domain.model.PkceCodeVerifierMethodClientConfig;
import com.airlock.iam.oauth2.domain.model.client.communication.OAuth2TokenRequestClientSecretProcessor;
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.license.PluginLicenseRestriction;
import com.airlock.iam.plugin.api.annotation.property.EnumProperty;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
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 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URI;
import java.util.List;
import java.util.Optional;

@Plugin(
   description = "OAuth 2.0 Flow Client Settings. The settings define the OAuth 2.0 handshake and can be referenced in flows through the provider ID. When the OAuth 2.0 authorization was successful, the OAuth 2.0 Access Token is stored in the user session and can be used by the plugin OAuth 2.0 Tokens Map in the ID Propagation to provide the Access Token to for the backends.",
   displayName = "OAuth 2.0 Flow Client"
)
@PluginLicenseRestriction(moduleAffiliation = "OAuthClient")
@Groups(
   groupSequence = {
         "Basic Settings",
         "Authorization Request",
         "Access Token Request",
         "Resource Mappings",
         "Authentication and Authorization",
         "Account Linking and Registration",
         "Integration"
   },
   openGroups = {"Basic Settings", "Authorization Request", "Access Token Request", "Resource Mappings"}
)
public final class OAuth2SsoFlowClientSettings extends OAuth2SsoFlowClientSettingsBase {
   public static final String OAUTH2_CLIENT_SETTINGS_DISPLAY_NAME = "OAuth 2.0 Flow Client";
   @PluginProperty(description = "HTTP client used for token endpoint requests.", displayName = "HTTP Client", optional = false)
   @GroupProperty(groupIdentifier = "Access Token Request")
   private HttpClientConfig httpClient;
   @PluginProperty(
      description = "Specifies how the client secret is included in requests to the token and pushed authorization request (PAR) endpoints.<p/><a target=\"_blank\" href=\"https://tools.ietf.org/html/rfc6749\">RFC 6749</a> suggests to use the HTTP Basic authentication scheme ('OAuth 2.0 Basic Auth Client Secret').",
      optional = true,
      defaultValue = OAuth2BasicAuthClientSecretConfig.class,
      displayName = "Token/PAR Endpoint Authentication"
   )
   @GroupProperty(groupIdentifier = "Access Token Request")
   private OAuth2TokenRequestClientSecretProcessor tokenEndpointAuthentication;
   @StringProperty(
      description = "Authorization endpoint URL to obtain Authorization Codes from.",
      exampleValues = {
            "https://airlock.iam/auth/ui/app/auth/oauth2/authorization-servers/asId/authorize",
            "https://airlock.iam/auth/oauth2/v3/asId/authorize",
            "https://accounts.google.com/o/oauth2/auth",
            "https://login.live.com/oauth20_authorize.srf"
      },
      optional = false
   )
   @GroupProperty(groupIdentifier = "Authorization Request")
   private String authorizationEndpointURL;
   @StringProperty(
      description = "The pushed authorization request endpoint URI to send the Authorization Request to. If this property is set, the OAuth2 client will send all Authorization Requests according to the PAR specification as defined in <a href=\"https://www.rfc-editor.org/rfc/rfc9126.html\" target=\"_blank\">RFC 9126</a>.",
      exampleValues = {"https://airlock.iam/auth/rest/oauth2/authorization-servers/asId/par", "https://as.example.org/as/par"},
      optional = true
   )
   @GroupProperty(groupIdentifier = "Authorization Request")
   private String pushedAuthorizationRequestEndpointURL;
   @StringProperty(
      description = "Token endpoint URL to get Access and Refresh Tokens.",
      exampleValues = {
            "https://airlock.com/auth/rest/oauth2/authorization-servers/asId/token",
            "https://accounts.google.com/o/oauth2/token",
            "https://login.live.com/oauth20_token.srf"
      },
      optional = false
   )
   @GroupProperty(groupIdentifier = "Access Token Request")
   private String tokenEndpointURL;
   @StringListProperty(
      description = "Scopes to request from the authorization endpoint. Note that some authorization servers deny requests with no requested scopes.<p>Scopes may only contain the following characters: <code>0-9, A-Z, a-z, !, #, $, %, &, ', (, ), *, +, ',', -, ., /, :, ;, &lt;, &gt;, =, ?, @, [, ], ^, _, `, {, }, |, ~</code></p>",
      validationRegex = "[\\x21\\x23-\\x5B\\x5D-\\x7E]+",
      exampleElements = "userinfo",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Authorization Request")
   private List<String> scopesToRequest;
   @EnumProperty(displayName = "PKCE Challenge Method", description = "Configures the PKCE challenge method.", optional = true, defaultValue = "S256")
   @GroupProperty(groupIdentifier = "Authorization Request")
   private PkceCodeVerifierMethodClientConfig pkceChallengeMethod;

   OAuth2SsoFlowClientSettings(
      String clientId,
      String clientSecret,
      OAuth2RequestBuilderConfig accessTokenRequestMethod,
      OAuth2LoggingSettings loggingSettings,
      boolean enableAccountLinking,
      String autoLinkExistingUsersContextDataField,
      AccountRegistrationConfig accountRegistrationConfig,
      OAuth2ProviderIdentifierConfig providerId,
      OAuth2RedirectUriConfig clientRedirectURI,
      List<OAuth2ClientResourceRequest> resourceRequests,
      AccountLinkingSelfServiceConfig accountLinkingSelfService,
      RedFlagConfig missingAccountLinkRedFlag,
      HttpClientConfig httpClient,
      OAuth2TokenRequestClientSecretProcessor tokenEndpointAuthentication,
      String authorizationEndpointURL,
      String tokenEndpointURL,
      String pushedAuthorizationRequestEndpointURL,
      List<String> scopesToRequest,
      PkceCodeVerifierMethodClientConfig pkceChallengeMethod
   ) {
      super(
         clientId,
         clientSecret,
         accessTokenRequestMethod,
         loggingSettings,
         enableAccountLinking,
         autoLinkExistingUsersContextDataField,
         accountRegistrationConfig,
         providerId,
         clientRedirectURI,
         resourceRequests,
         accountLinkingSelfService,
         missingAccountLinkRedFlag
      );
      this.httpClient = httpClient;
      this.tokenEndpointAuthentication = tokenEndpointAuthentication;
      this.authorizationEndpointURL = authorizationEndpointURL;
      this.tokenEndpointURL = tokenEndpointURL;
      this.pushedAuthorizationRequestEndpointURL = pushedAuthorizationRequestEndpointURL;
      this.scopesToRequest = scopesToRequest;
      this.pkceChallengeMethod = pkceChallengeMethod;
   }

   @ConfigurationValidator
   static void checkExactlyOneUsernameResource(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      OAuth2ResourcesConfigValidator.builder()
         .config(config)
         .env(env)
         .context(context)
         .resourceRequestsField("resourceRequests")
         .containedResourcesField("containedResources")
         .build()
         .checkExactlyOneUsernameResourceRequest();
   }

   @Override
   public Optional<URI> getPushedAuthorizationRequestEndpointURL() {
      return Optional.ofNullable(this.pushedAuthorizationRequestEndpointURL).map(URI::create);
   }

   @Override
   public PkceCodeVerifierMethodClientConfig pkceChallengeMethod() {
      return this.pkceChallengeMethod;
   }

   @Override
   public HttpClientConfig getHttpClient() {
      return this.httpClient;
   }

   @Override
   public OAuth2TokenRequestClientSecretProcessor getTokenEndpointAuthentication() {
      return this.tokenEndpointAuthentication;
   }

   public String getAuthorizationEndpointURL() {
      return this.authorizationEndpointURL;
   }

   public String getTokenEndpointURL() {
      return this.tokenEndpointURL;
   }

   public List<String> getScopesToRequest() {
      return this.scopesToRequest;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2SsoFlowClientSettingsBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String clientId;
      @SuppressFBWarnings(justification = "generated code")
      private String clientSecret;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2RequestBuilderConfig accessTokenRequestMethod;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2LoggingSettings loggingSettings;
      @SuppressFBWarnings(justification = "generated code")
      private boolean enableAccountLinking;
      @SuppressFBWarnings(justification = "generated code")
      private String autoLinkExistingUsersContextDataField;
      @SuppressFBWarnings(justification = "generated code")
      private AccountRegistrationConfig accountRegistrationConfig;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2ProviderIdentifierConfig providerId;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2RedirectUriConfig clientRedirectURI;
      @SuppressFBWarnings(justification = "generated code")
      private List<OAuth2ClientResourceRequest> resourceRequests;
      @SuppressFBWarnings(justification = "generated code")
      private AccountLinkingSelfServiceConfig accountLinkingSelfService;
      @SuppressFBWarnings(justification = "generated code")
      private RedFlagConfig missingAccountLinkRedFlag;
      @SuppressFBWarnings(justification = "generated code")
      private HttpClientConfig httpClient;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2TokenRequestClientSecretProcessor tokenEndpointAuthentication;
      @SuppressFBWarnings(justification = "generated code")
      private String authorizationEndpointURL;
      @SuppressFBWarnings(justification = "generated code")
      private String tokenEndpointURL;
      @SuppressFBWarnings(justification = "generated code")
      private String pushedAuthorizationRequestEndpointURL;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> scopesToRequest;
      @SuppressFBWarnings(justification = "generated code")
      private PkceCodeVerifierMethodClientConfig pkceChallengeMethod;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder clientId(String clientId) {
         this.clientId = clientId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder clientSecret(String clientSecret) {
         this.clientSecret = clientSecret;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder accessTokenRequestMethod(OAuth2RequestBuilderConfig accessTokenRequestMethod) {
         this.accessTokenRequestMethod = accessTokenRequestMethod;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder loggingSettings(OAuth2LoggingSettings loggingSettings) {
         this.loggingSettings = loggingSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder enableAccountLinking(boolean enableAccountLinking) {
         this.enableAccountLinking = enableAccountLinking;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder autoLinkExistingUsersContextDataField(String autoLinkExistingUsersContextDataField) {
         this.autoLinkExistingUsersContextDataField = autoLinkExistingUsersContextDataField;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder accountRegistrationConfig(AccountRegistrationConfig accountRegistrationConfig) {
         this.accountRegistrationConfig = accountRegistrationConfig;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder providerId(OAuth2ProviderIdentifierConfig providerId) {
         this.providerId = providerId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder clientRedirectURI(OAuth2RedirectUriConfig clientRedirectURI) {
         this.clientRedirectURI = clientRedirectURI;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder resourceRequests(List<OAuth2ClientResourceRequest> resourceRequests) {
         this.resourceRequests = resourceRequests;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder accountLinkingSelfService(AccountLinkingSelfServiceConfig accountLinkingSelfService) {
         this.accountLinkingSelfService = accountLinkingSelfService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder missingAccountLinkRedFlag(RedFlagConfig missingAccountLinkRedFlag) {
         this.missingAccountLinkRedFlag = missingAccountLinkRedFlag;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder httpClient(HttpClientConfig httpClient) {
         this.httpClient = httpClient;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder tokenEndpointAuthentication(
         OAuth2TokenRequestClientSecretProcessor tokenEndpointAuthentication
      ) {
         this.tokenEndpointAuthentication = tokenEndpointAuthentication;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder authorizationEndpointURL(String authorizationEndpointURL) {
         this.authorizationEndpointURL = authorizationEndpointURL;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder tokenEndpointURL(String tokenEndpointURL) {
         this.tokenEndpointURL = tokenEndpointURL;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder pushedAuthorizationRequestEndpointURL(String pushedAuthorizationRequestEndpointURL) {
         this.pushedAuthorizationRequestEndpointURL = pushedAuthorizationRequestEndpointURL;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder scopesToRequest(List<String> scopesToRequest) {
         this.scopesToRequest = scopesToRequest;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder pkceChallengeMethod(PkceCodeVerifierMethodClientConfig pkceChallengeMethod) {
         this.pkceChallengeMethod = pkceChallengeMethod;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoFlowClientSettings build() {
         return new OAuth2SsoFlowClientSettings(
            this.clientId,
            this.clientSecret,
            this.accessTokenRequestMethod,
            this.loggingSettings,
            this.enableAccountLinking,
            this.autoLinkExistingUsersContextDataField,
            this.accountRegistrationConfig,
            this.providerId,
            this.clientRedirectURI,
            this.resourceRequests,
            this.accountLinkingSelfService,
            this.missingAccountLinkRedFlag,
            this.httpClient,
            this.tokenEndpointAuthentication,
            this.authorizationEndpointURL,
            this.tokenEndpointURL,
            this.pushedAuthorizationRequestEndpointURL,
            this.scopesToRequest,
            this.pkceChallengeMethod
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2SsoFlowClientSettings.OAuth2SsoFlowClientSettingsBuilder(clientId="
            + this.clientId
            + ", clientSecret="
            + this.clientSecret
            + ", accessTokenRequestMethod="
            + this.accessTokenRequestMethod
            + ", loggingSettings="
            + this.loggingSettings
            + ", enableAccountLinking="
            + this.enableAccountLinking
            + ", autoLinkExistingUsersContextDataField="
            + this.autoLinkExistingUsersContextDataField
            + ", accountRegistrationConfig="
            + this.accountRegistrationConfig
            + ", providerId="
            + this.providerId
            + ", clientRedirectURI="
            + this.clientRedirectURI
            + ", resourceRequests="
            + this.resourceRequests
            + ", accountLinkingSelfService="
            + this.accountLinkingSelfService
            + ", missingAccountLinkRedFlag="
            + this.missingAccountLinkRedFlag
            + ", httpClient="
            + this.httpClient
            + ", tokenEndpointAuthentication="
            + this.tokenEndpointAuthentication
            + ", authorizationEndpointURL="
            + this.authorizationEndpointURL
            + ", tokenEndpointURL="
            + this.tokenEndpointURL
            + ", pushedAuthorizationRequestEndpointURL="
            + this.pushedAuthorizationRequestEndpointURL
            + ", scopesToRequest="
            + this.scopesToRequest
            + ", pkceChallengeMethod="
            + this.pkceChallengeMethod
            + ")";
      }
   }
}
