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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.common.api.application.configuration.claims.ClaimsValidatorConfig;
import com.airlock.iam.common.domain.service.jwt.jwks.JwksSignatureValidator;
import com.airlock.iam.common.domain.service.jwt.signature.HttpClientRemoteJWKSetLoader;
import com.airlock.iam.core.misc.util.httpclient.HttpClientConfig;
import com.airlock.iam.flow.api.application.configuration.redflag.RedFlagConfig;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.ClientAuthenticationMethod;
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.AbstractOAuth2RemoteResourceConfig;
import com.airlock.iam.oauth2.application.configuration.client.OAuth2ClientResourceRequest;
import com.airlock.iam.oauth2.application.configuration.client.OAuth2PostLogoutRedirectUriConfig;
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.client.OAuth2SpaPostLogoutRedirectUriConfig;
import com.airlock.iam.oauth2.application.configuration.client.OpenIDConnectDiscoverySignatureValidator;
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.AudienceClaimValidationMethod;
import com.airlock.iam.oauth2.domain.model.client.communication.OAuth2TokenRequestClientSecretProcessor;
import com.airlock.iam.oauth2.domain.model.client.communication.OpenIDConnectIdTokenSignatureValidator;
import com.airlock.iam.oauth2.infrastructure.discovery.OpenIDConnectProviderMetadataCache;
import com.airlock.iam.oauth2.infrastructure.discovery.OpenIDConnectProviderMetadataRequestClient;
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.BooleanProperty;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.IntegerProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.annotation.property.TestletProperty;
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 com.nimbusds.oauth2.sdk.id.Identifier;
import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import org.apache.commons.collections4.CollectionUtils;

@Plugin(
   description = "OIDC Discovery Flow Client Settings.<p>The information from the discovery and JWKs requests will be lazy loaded and then cached for the configured time (see property 'Cache Refresh Time [minutes]').</p>The settings define the OIDC handshake and can be referenced in flows through the provider id. When the OpenID Connect authorization was successful, the OAuth 2.0 Access Token and OpenID Connect ID 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 tokens to the backends.",
   displayName = "OIDC Discovery Flow Client"
)
@Groups(
   groupSequence = {
         "Basic Settings",
         "Communication and Discovery",
         "Authorization Request",
         "Access Token Request",
         "ID Token",
         "Resource Mappings",
         "Authentication and Authorization",
         "Account Linking and Registration",
         "Logout",
         "Integration"
   },
   openGroups = {"Basic Settings", "Communication and Discovery", "Resource Mappings"}
)
@PluginLicenseRestriction(moduleAffiliation = "OAuthClient")
public class OpenIDConnectDiscoverySsoFlowClientSettings extends OpenIDConnectSsoFlowClientSettingsBase {
   static final String COMMUNICATION_AND_DISCOVERY = "Communication and Discovery";
   @PluginProperty(
      description = "HTTP client used for discovery, JWKs, token and pushed authorization request endpoint requests.",
      displayName = "HTTP Client",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Communication and Discovery")
   private HttpClientConfig httpClient;
   public static final String HTTP_CLIENT_FIELD = "httpClient";
   @StringProperty(
      description = "Discovery endpoint URL to obtain the configuration from. The data will be lazy loaded when the first login with this provider is initiated.",
      displayName = "Discovery Endpoint URL",
      exampleValues = {
            "https://airlock.iam/auth/oauth2/clientId/.well-known/openid-configuration",
            "https://airlock.iam/auth/rest/oauth2/authorization-servers/asId/.well-known/openid-configuration",
            "https://accounts.google.com/.well-known/openid-configuration"
      },
      optional = false
   )
   @GroupProperty(groupIdentifier = "Communication and Discovery")
   @TestletProperty(testletClass = OpenIDConnectDiscoveryTestlet.class, buttonToolTip = "Test the configuration using OpenID Connect Discovery.")
   private String discoveryEndpointURL;
   public static final String DISCOVERY_URL_FIELD = "discoveryEndpointURL";
   public static final String DISCOVERY_ENDPOINT_URL = "Discovery Endpoint URL";
   @IntegerProperty(
      description = "Time in minutes after which cached data from the discovery endpoint URL and the JWKS URL is refreshed.\n<p>The refresh is asynchronous: the first request after this time still receives the previously cached data and triggers a reload in the background.</p>\n<p>Subsequent requests receive the refreshed data.</p>\n<p>Data not requested for twice this time expires and is reloaded synchronously on the next request, so served data is never older than twice this time.</p>\n<p>The JWKS data is additionally reloaded if a key is not yet known, or if a signature check with a known key fails.</p>\n<p>If a refresh fails because of an error, the previously fetched data is kept while the entry is still in use.</p>",
      displayName = "Cache Refresh Time [minutes]",
      exampleValues = {60, 1440, 2880},
      minValue = 1,
      optional = true,
      defaultValue = 240
   )
   @GroupProperty(groupIdentifier = "Communication and Discovery")
   private int cacheRefreshTimeInMinutes;
   static final String CACHE_REFRESH_TIME_MINUTES_DISPLAY_NAME = "Cache Refresh Time [minutes]";
   @PluginProperty(
      description = "ID token signature validation. If left empty the id token's signature will be validated as follows:<ul><li>HMAC based signatures (HS256, HS384, HS512) will be validated using the client secret.</li><li>Asymmetric key based signatures (RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES256K, ES384, ES512) will be validated using the matching public key from the JWKs URL. The JWKs URL included in the discovery data is used.</li></ul>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "ID Token")
   private OpenIDConnectIdTokenSignatureValidator signatureValidator;
   @PluginProperty(
      description = "Specifies how the client secret is included in requests to the token and pushed authorization request (PAR) endpoints.<p/>If left empty the supported token endpoint auth methods as specified by the discovery endpoint will be used in the following priority:<ol><li>client_secret_basic</li><li>client_secret_post</li></ol><p>For security reasons client authentication must always be used.</p>",
      optional = true,
      displayName = "Token/PAR Endpoint Authentication"
   )
   @GroupProperty(groupIdentifier = "Access Token Request")
   private OAuth2TokenRequestClientSecretProcessor tokenEndpointAuthentication;
   public static final String CLIENT_SECRET_CONFIG_FIELD = "tokenEndpointAuthentication";
   @BooleanProperty(
      description = "If enabled, IAM notifies the AS that an end-user has logged out (RP-initiated logout). This is done by calling the end session endpoint as specified by the discovery endpoint.<p>The end session endpoint is not called if not defined by the discovery endpoint, regardless of this property.</p>",
      displayName = "Call End Session Endpoint",
      optional = true,
      defaultValue = "false"
   )
   @GroupProperty(groupIdentifier = "Logout")
   private boolean callEndSessionEndpoint;
   private static final String CALL_END_SESSION_ENDPOINT_DISPLAY_NAME = "Call End Session Endpoint";
   private static final String CALL_END_SESSION_ENDPOINT_PROPERTY = "callEndSessionEndpoint";
   @BooleanProperty(
      description = "<p>Configures whether the client should enable Pushed Authorization Requests (PAR; <a href=\"https://www.rfc-editor.org/rfc/rfc9126.html\" target=\"_blank\">RFC 9126</a>) extension if the OIDC Authorization Server supports it as an optional extension.</p><p><strong>Note:</strong> This property is disregarded if the discovery document has the option <code>require_pushed_authorization_requests</code> set to <code>true</code> (PAR is always used if that flag is enabled).</p>",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Authorization Request")
   private boolean preferPushedAuthorizationRequests;
   @BooleanProperty(
      displayName = "Use PKCE if offered",
      description = "<p>Configures whether the client should enable PKCE if it is offered by the OIDC Authorization Server.</p>\n<p>Only S256 is allowed as the challenge method. If this setting is active and PKCE is offered without S256 as challenge method,\nthe flow will fail.</p>\n",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Authorization Request")
   private boolean usePkceIfOffered;
   private OpenIDConnectProviderMetadataCache metadataCache;
   private HttpClientRemoteJWKSetLoader jwkSetLoader;

   public OpenIDConnectDiscoverySsoFlowClientSettings(
      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,
      List<ClaimRequestConfig> claimsToRequest,
      List<String> acrValuesClaim,
      boolean includeNonce,
      boolean includeLanguageParameter,
      int maxAuthenticationAge,
      List<String> prompt,
      String customIssuerClaim,
      AudienceClaimValidationMethod audienceClaimValidationMethod,
      String customAudienceClaim,
      boolean enableAcrValidation,
      List<ClaimsValidatorConfig> additionalClaimValidators,
      List<String> scopesToRequest,
      List<AbstractOAuth2RemoteResourceConfig> idTokenResources,
      OAuth2PostLogoutRedirectUriConfig postLogoutRedirectUrl,
      HttpClientConfig httpClient,
      String discoveryEndpointURL,
      int cacheRefreshTimeInMinutes,
      OpenIDConnectIdTokenSignatureValidator signatureValidator,
      OAuth2TokenRequestClientSecretProcessor tokenEndpointAuthentication,
      boolean callEndSessionEndpoint,
      List<OpenIdConnectAuthorizationRequestParameterConfig> customAuthorizationRequestParameters,
      boolean preferPushedAuthorizationRequests,
      boolean usePkceIfOffered
   ) {
      super(
         clientId,
         clientSecret,
         accessTokenRequestMethod,
         loggingSettings,
         enableAccountLinking,
         autoLinkExistingUsersContextDataField,
         accountRegistrationConfig,
         providerId,
         clientRedirectURI,
         resourceRequests,
         accountLinkingSelfService,
         missingAccountLinkRedFlag,
         claimsToRequest,
         acrValuesClaim,
         includeNonce,
         includeLanguageParameter,
         maxAuthenticationAge,
         prompt,
         customIssuerClaim,
         audienceClaimValidationMethod,
         customAudienceClaim,
         enableAcrValidation,
         additionalClaimValidators,
         scopesToRequest,
         idTokenResources,
         postLogoutRedirectUrl,
         customAuthorizationRequestParameters
      );
      this.httpClient = httpClient;
      this.discoveryEndpointURL = discoveryEndpointURL;
      this.cacheRefreshTimeInMinutes = cacheRefreshTimeInMinutes;
      this.signatureValidator = signatureValidator;
      this.tokenEndpointAuthentication = tokenEndpointAuthentication;
      this.callEndSessionEndpoint = callEndSessionEndpoint;
      this.preferPushedAuthorizationRequests = preferPushedAuthorizationRequests;
      this.usePkceIfOffered = usePkceIfOffered;
   }

   @Override
   public String getAuthorizationEndpointURL() {
      return this.metadata().getAuthorizationEndpointURI().toString();
   }

   @Override
   public Optional<URI> getPushedAuthorizationRequestEndpointURL() {
      if (!this.shouldUsePushedAuthorizationForCommunication()) {
         return Optional.empty();
      } else if (this.metadata().getPushedAuthorizationRequestEndpointURI() == null) {
         throw new FailureException("AS requires pushed authorization requests but does not provide a URI");
      } else {
         return Optional.of(this.metadata().getPushedAuthorizationRequestEndpointURI());
      }
   }

   @Override
   public String getTokenEndpointURL() {
      return this.metadata().getTokenEndpointURI().toString();
   }

   @Override
   public URI getEndSessionEndpoint() {
      return this.metadata().getEndSessionEndpointURI();
   }

   @Override
   public boolean supportsEndSessionEndpoint() {
      return this.callEndSessionEndpoint && this.getEndSessionEndpoint() != null;
   }

   @Override
   public OAuth2TokenRequestClientSecretProcessor getTokenEndpointAuthentication() {
      return this.tokenEndpointAuthentication != null ? this.tokenEndpointAuthentication : new OpenIDConnectDiscoveryClientSecretConfig(this.metadata());
   }

   @Override
   public OpenIDConnectIdTokenSignatureValidator getSignatureValidator() {
      return this.signatureValidator != null
         ? this.signatureValidator
         : new OpenIDConnectDiscoverySignatureValidator(
            new JwksSignatureValidator(this.jwkSetLoader, this.metadata().getJWKSetURI()), this.loggerFor(OpenIDConnectDiscoverySignatureValidator.class)
         );
   }

   @SuppressFBWarnings("UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR")
   private OIDCProviderMetadata metadata() {
      return this.metadataCache.cached();
   }

   private boolean shouldUsePushedAuthorizationForCommunication() {
      return this.preferPushedAuthorizationRequests && this.metadata().getPushedAuthorizationRequestEndpointURI() != null
         || this.metadata().requiresPushedAuthorizationRequests();
   }

   @Override
   void initializePlugin() {
      super.initializeHttpClient();
      OpenIDConnectProviderMetadataRequestClient metadataRequestClient = new OpenIDConnectProviderMetadataRequestClient(
         this.getHttpClientInstance(), URI.create(this.discoveryEndpointURL)
      );
      Optional<ClientAuthenticationMethod> configuredClientAuthMethod = Optional.ofNullable(this.tokenEndpointAuthentication)
         .map(OAuth2TokenRequestClientSecretProcessor::clientAuthenticationMethod);
      this.metadataCache = new OpenIDConnectProviderMetadataCache(
         metadataRequestClient, this.getProviderId(), Duration.ofMinutes(this.cacheRefreshTimeInMinutes), configuredClientAuthMethod
      );
      this.jwkSetLoader = new HttpClientRemoteJWKSetLoader(this.getHttpClientInstance(), Duration.ofMinutes(this.cacheRefreshTimeInMinutes));
   }

   @ConfigurationValidator
   static void validateEndSessionEndpoint(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      PluginConfiguration postLogoutRedirectUriPlugin = PluginPropertyValueReader.effectivePluginPropertyValue(config, "postLogoutRedirectUrl", env, context);
      if (postLogoutRedirectUriPlugin != null && postLogoutRedirectUriPlugin.isOfPluginClass(OAuth2SpaPostLogoutRedirectUriConfig.class)) {
         Boolean callEndSessionEndpoint = BooleanPropertyValueReader.effectiveBooleanPropertyValue(config, "callEndSessionEndpoint", env, context);
         if (callEndSessionEndpoint != null && !callEndSessionEndpoint) {
            throw new PropertyConstraintViolationException(
               "postLogoutRedirectUrl",
               config,
               env,
               context,
               "Configuring a 'Post Logout Redirect URL' is only effective if also 'Call End Session Endpoint' is enabled"
            );
         }
      }
   }

   @Override
   public PkceCodeVerifierMethodClientConfig pkceChallengeMethod() {
      if (this.usePkceIfOffered && !CollectionUtils.isEmpty(this.metadata().getCodeChallengeMethods())) {
         Optional<CodeChallengeMethod> s256ChallengeMethod = this.metadata()
            .getCodeChallengeMethods()
            .stream()
            .filter(CodeChallengeMethod.S256::equals)
            .findFirst();
         if (s256ChallengeMethod.isEmpty()) {
            throw new FailureException(
               "Mandatory method 'S256' missing in code_challenge_methods_supported: " + Identifier.toStringList(this.metadata().getCodeChallengeMethods())
            );
         } else {
            return PkceCodeVerifierMethodClientConfig.S256;
         }
      } else {
         return PkceCodeVerifierMethodClientConfig.DISABLED;
      }
   }

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

   public String getDiscoveryEndpointURL() {
      return this.discoveryEndpointURL;
   }

   public int getCacheRefreshTimeInMinutes() {
      return this.cacheRefreshTimeInMinutes;
   }

   public boolean isPreferPushedAuthorizationRequests() {
      return this.preferPushedAuthorizationRequests;
   }

   public boolean isUsePkceIfOffered() {
      return this.usePkceIfOffered;
   }

   public OpenIDConnectProviderMetadataCache getMetadataCache() {
      return this.metadataCache;
   }

   public HttpClientRemoteJWKSetLoader getJwkSetLoader() {
      return this.jwkSetLoader;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OpenIDConnectDiscoverySsoFlowClientSettingsBuilder {
      @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 List<ClaimRequestConfig> claimsToRequest;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> acrValuesClaim;
      @SuppressFBWarnings(justification = "generated code")
      private boolean includeNonce;
      @SuppressFBWarnings(justification = "generated code")
      private boolean includeLanguageParameter;
      @SuppressFBWarnings(justification = "generated code")
      private int maxAuthenticationAge;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> prompt;
      @SuppressFBWarnings(justification = "generated code")
      private String customIssuerClaim;
      @SuppressFBWarnings(justification = "generated code")
      private AudienceClaimValidationMethod audienceClaimValidationMethod;
      @SuppressFBWarnings(justification = "generated code")
      private String customAudienceClaim;
      @SuppressFBWarnings(justification = "generated code")
      private boolean enableAcrValidation;
      @SuppressFBWarnings(justification = "generated code")
      private List<ClaimsValidatorConfig> additionalClaimValidators;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> scopesToRequest;
      @SuppressFBWarnings(justification = "generated code")
      private List<AbstractOAuth2RemoteResourceConfig> idTokenResources;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2PostLogoutRedirectUriConfig postLogoutRedirectUrl;
      @SuppressFBWarnings(justification = "generated code")
      private HttpClientConfig httpClient;
      @SuppressFBWarnings(justification = "generated code")
      private String discoveryEndpointURL;
      @SuppressFBWarnings(justification = "generated code")
      private int cacheRefreshTimeInMinutes;
      @SuppressFBWarnings(justification = "generated code")
      private OpenIDConnectIdTokenSignatureValidator signatureValidator;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2TokenRequestClientSecretProcessor tokenEndpointAuthentication;
      @SuppressFBWarnings(justification = "generated code")
      private boolean callEndSessionEndpoint;
      @SuppressFBWarnings(justification = "generated code")
      private List<OpenIdConnectAuthorizationRequestParameterConfig> customAuthorizationRequestParameters;
      @SuppressFBWarnings(justification = "generated code")
      private boolean preferPushedAuthorizationRequests;
      @SuppressFBWarnings(justification = "generated code")
      private boolean usePkceIfOffered;

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

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

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

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

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

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

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

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

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

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder claimsToRequest(
         List<ClaimRequestConfig> claimsToRequest
      ) {
         this.claimsToRequest = claimsToRequest;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder acrValuesClaim(List<String> acrValuesClaim) {
         this.acrValuesClaim = acrValuesClaim;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder includeNonce(boolean includeNonce) {
         this.includeNonce = includeNonce;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder includeLanguageParameter(
         boolean includeLanguageParameter
      ) {
         this.includeLanguageParameter = includeLanguageParameter;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder maxAuthenticationAge(int maxAuthenticationAge) {
         this.maxAuthenticationAge = maxAuthenticationAge;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder prompt(List<String> prompt) {
         this.prompt = prompt;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder customIssuerClaim(String customIssuerClaim) {
         this.customIssuerClaim = customIssuerClaim;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder audienceClaimValidationMethod(
         AudienceClaimValidationMethod audienceClaimValidationMethod
      ) {
         this.audienceClaimValidationMethod = audienceClaimValidationMethod;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder customAudienceClaim(String customAudienceClaim) {
         this.customAudienceClaim = customAudienceClaim;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder enableAcrValidation(boolean enableAcrValidation) {
         this.enableAcrValidation = enableAcrValidation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder additionalClaimValidators(
         List<ClaimsValidatorConfig> additionalClaimValidators
      ) {
         this.additionalClaimValidators = additionalClaimValidators;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder idTokenResources(
         List<AbstractOAuth2RemoteResourceConfig> idTokenResources
      ) {
         this.idTokenResources = idTokenResources;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder postLogoutRedirectUrl(
         OAuth2PostLogoutRedirectUriConfig postLogoutRedirectUrl
      ) {
         this.postLogoutRedirectUrl = postLogoutRedirectUrl;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder discoveryEndpointURL(String discoveryEndpointURL) {
         this.discoveryEndpointURL = discoveryEndpointURL;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder cacheRefreshTimeInMinutes(
         int cacheRefreshTimeInMinutes
      ) {
         this.cacheRefreshTimeInMinutes = cacheRefreshTimeInMinutes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder signatureValidator(
         OpenIDConnectIdTokenSignatureValidator signatureValidator
      ) {
         this.signatureValidator = signatureValidator;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder callEndSessionEndpoint(
         boolean callEndSessionEndpoint
      ) {
         this.callEndSessionEndpoint = callEndSessionEndpoint;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder customAuthorizationRequestParameters(
         List<OpenIdConnectAuthorizationRequestParameterConfig> customAuthorizationRequestParameters
      ) {
         this.customAuthorizationRequestParameters = customAuthorizationRequestParameters;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder preferPushedAuthorizationRequests(
         boolean preferPushedAuthorizationRequests
      ) {
         this.preferPushedAuthorizationRequests = preferPushedAuthorizationRequests;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder usePkceIfOffered(boolean usePkceIfOffered) {
         this.usePkceIfOffered = usePkceIfOffered;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OpenIDConnectDiscoverySsoFlowClientSettings build() {
         return new OpenIDConnectDiscoverySsoFlowClientSettings(
            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.claimsToRequest,
            this.acrValuesClaim,
            this.includeNonce,
            this.includeLanguageParameter,
            this.maxAuthenticationAge,
            this.prompt,
            this.customIssuerClaim,
            this.audienceClaimValidationMethod,
            this.customAudienceClaim,
            this.enableAcrValidation,
            this.additionalClaimValidators,
            this.scopesToRequest,
            this.idTokenResources,
            this.postLogoutRedirectUrl,
            this.httpClient,
            this.discoveryEndpointURL,
            this.cacheRefreshTimeInMinutes,
            this.signatureValidator,
            this.tokenEndpointAuthentication,
            this.callEndSessionEndpoint,
            this.customAuthorizationRequestParameters,
            this.preferPushedAuthorizationRequests,
            this.usePkceIfOffered
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OpenIDConnectDiscoverySsoFlowClientSettings.OpenIDConnectDiscoverySsoFlowClientSettingsBuilder(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
            + ", claimsToRequest="
            + this.claimsToRequest
            + ", acrValuesClaim="
            + this.acrValuesClaim
            + ", includeNonce="
            + this.includeNonce
            + ", includeLanguageParameter="
            + this.includeLanguageParameter
            + ", maxAuthenticationAge="
            + this.maxAuthenticationAge
            + ", prompt="
            + this.prompt
            + ", customIssuerClaim="
            + this.customIssuerClaim
            + ", audienceClaimValidationMethod="
            + this.audienceClaimValidationMethod
            + ", customAudienceClaim="
            + this.customAudienceClaim
            + ", enableAcrValidation="
            + this.enableAcrValidation
            + ", additionalClaimValidators="
            + this.additionalClaimValidators
            + ", scopesToRequest="
            + this.scopesToRequest
            + ", idTokenResources="
            + this.idTokenResources
            + ", postLogoutRedirectUrl="
            + this.postLogoutRedirectUrl
            + ", httpClient="
            + this.httpClient
            + ", discoveryEndpointURL="
            + this.discoveryEndpointURL
            + ", cacheRefreshTimeInMinutes="
            + this.cacheRefreshTimeInMinutes
            + ", signatureValidator="
            + this.signatureValidator
            + ", tokenEndpointAuthentication="
            + this.tokenEndpointAuthentication
            + ", callEndSessionEndpoint="
            + this.callEndSessionEndpoint
            + ", customAuthorizationRequestParameters="
            + this.customAuthorizationRequestParameters
            + ", preferPushedAuthorizationRequests="
            + this.preferPushedAuthorizationRequests
            + ", usePkceIfOffered="
            + this.usePkceIfOffered
            + ")";
      }
   }
}
