package com.airlock.iam.factor.application.configuration.fido;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.application.configuration.valueprovider.StringValueProviderConfig;
import com.airlock.iam.common.api.application.configuration.valueprovider.UserInformationProviderConfig;
import com.airlock.iam.core.application.configuration.fido.FidoGlobalSettings;
import com.airlock.iam.factor.domain.model.fido.FidoOrigin;
import com.airlock.iam.factor.domain.model.fido.FidoRelyingPartyName;
import com.airlock.iam.factor.domain.model.fido.FidoTransport;
import com.airlock.iam.factor.domain.model.fido.FidoUrlOrigin;
import com.airlock.iam.factor.domain.model.fido.SupportedCoseAlgorithmIdentifier;
import com.airlock.iam.factor.domain.model.fido.registration.FidoAttestationType;
import com.airlock.iam.factor.domain.model.fido.registration.FidoAuthenticatorType;
import com.airlock.iam.factor.domain.model.fido.registration.FidoUserVerificationPreference;
import com.airlock.iam.factor.domain.model.fido.registration.ResidentKeyRequirement;
import com.airlock.iam.fido.api.domain.model.FidoRelyingPartyId;
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.EnumProperty;
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.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 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.PropertyConfigurationException;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Plugin(
   displayName = "FIDO Settings",
   description = "Global settings related to FIDO.<p>Note that FIDO can behave differently on different operating systems or browsers. Some browsers offer only limited support for FIDO. This might lead to some FIDO features not working on all browsers.</p>"
)
@PluginLicenseRestriction(moduleAffiliation = "FIDO")
@Groups(
   groupSequence = {"Basic Settings", "Registration Settings", "Authentication / Approval Settings", "Advanced Settings"},
   openGroups = {"Basic Settings", "Registration Settings", "Authentication / Approval Settings"}
)
public final class FidoSettingsConfig implements FidoGlobalSettings {
   static final String REGISTRATION_SETTINGS = "Registration Settings";
   static final String AUTHENTICATION_APPROVAL_SETTINGS = "Authentication / Approval Settings";
   @PluginProperty(description = "Configures the repository to store FIDO data.", optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private FidoRepositoryConfig repository;
   @StringProperty(
      displayName = "Relying Party ID",
      description = "<p>The relying party ID (RPID) defines the scope of registered FIDO credentials. It determines the set of origins on which registered FIDO credentials may be used.</p><p>The RPID must either be the origin's domain or a registerable domain suffix. It is determined based on the fully-qualified domain name of the Airlock IAM (as seen by the browser or REST client).</p><p>Example:<ul><li>The browser communicates with IAM using the URLs of the form https://www.virtinc.com/auth/...</li><li>The RPID can then be either \"virtinc.com\" or \"www.virtinc.com\".</li><li>The RPID cannot be either of: \"abc.virtinc.com\", \"com\".</li></ul></p><p><b>Caution</b>: FIDO credentials are registered for a specific RPID and can only be used for this very RPID. The web domain can therefore not be changed without having to re-register the FIDO credentials!</p><p>If acceptable with your security requirements, we recommend to use only the domain suffix (e.g. \"virtinc.com\") as RPID. This gives you the freedom to use registered FIDO credentials on any subdomain (e.g. \"login.virtinc.com\" or \"auth.viritinc.com\").</p>",
      optional = false,
      maxLength = 253,
      exampleValues = {"www.virtinc.com", "mycompany.com", "secure.mycompany.com"}
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String relyingPartyId;
   static final String RELYING_PARTY_ID_FIELD = "relyingPartyId";
   @StringProperty(
      description = "<p>Name of the relying party. The name may be displayed by the web browser (or REST client).</p><p>If not defined, the relying party ID is used.</p>",
      optional = true,
      maxLength = 255,
      exampleValues = {"ACME Corporation", "Wonderful Widgets"}
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String relyingPartyName;
   static final String RELYING_PARTY_NAME_FIELD = "relyingPartyName";
   @PluginListProperty(
      displayName = "Additional Origins",
      description = "<p>List of additional origins that can be used with FIDO credentials.</p>\n\n<p>Each configured entry contributes an additional allowed origin (e.g. <code>android:apk-key-hash:&lt;hash&gt;</code> for Android) that is accepted during FIDO\nregistration and authentication, on top of the web origin derived from the Relying Party ID.</p>\n",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<FidoAdditionalOriginConfig> additionalOrigins;
   @PluginListProperty(
      displayName = "AAGUID Mappings",
      description = "List of recognized FIDO authenticators. Maps a unique ID provided by a FIDO authenticator (AAGUID) to a descriptive string displayed in the self-service credential list. <p>The order is important since in case multiple FIDO authenticators are defined with the same AAGUID, only the last entry in the list will be considered. This allows to override default information provided by the plugin' FIDO Default AAGUID Mappings' when configured in the list.</p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<FidoAaguidMappingsConfig> aaguidMappings;
   @PluginProperty(
      description = "<p>Specifies what user attribute (e.g. username, email address, etc.) is sent to the web browser (or REST client) during FIDO credential registration.</p><p>The user attribute is stored on the FIDO authenticator and may be displayed when using the registered FIDO credential. The stored user attribute cannot be changed on the FIDO authenticator, i.e. even if the attribute value changes in IAM (e.g. new email address), the value on the FIDO authenticator remains as it was during credential registration.</p><p>We strongly encourage to configure a User Information Provider if resident keys are required. This allows a user to easily identify and manage the credentials stored on the FIDO authenticator.</p><p>If no provider is configured or the configured provider provides no value, the string '-' is used.</p><p><b>Privacy warning:</b> The user attribute is stored on the FIDO authenticator. IAM cannot influence how secure the information is stored.</p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Registration Settings")
   private UserInformationProviderConfig userInformationProvider;
   @StringProperty(
      description = "This setting describes the Relying Party's requirements for client-side discoverable credentials (formerly known as resident credentials or resident keys):\n<ul>\n\t<li><code>discouraged</code>: This value indicates the Relying Party prefers creating a server-side credential, but will accept a client-side discoverable credential.</li>\n\t<li><code>preferred</code>: This value indicates the Relying Party strongly prefers creating a client-side discoverable credential, but will accept a server-side\n\tcredential. For example, user agents SHOULD guide the user through setting up user verification if needed to create a client-side discoverable credential in this case.\n\tThis takes precedence over the setting of \"User Verification\".</li>\n\t<li><code>required</code>: This value indicates the Relying Party requires a client-side discoverable credential, and is prepared to receive an error if a\n\tclient-side discoverable credential cannot be created. This is the recommended option and necessary for using FIDO passwordless authentication.</li>\n</ul>\nFor backward compatibility, the <code>requireResidentKey</code> is also added to the credential creation options (<code>true</code> if\n\"required\" is selected, and <code>false</code> otherwise).\n",
      defaultValue = "required",
      allowedValues = {"discouraged", "preferred", "required"},
      optional = true
   )
   @GroupProperty(groupIdentifier = "Registration Settings")
   private String residentKey;
   @EnumProperty(
      description = "<p>Restricts the FIDO authenticator types that can be used during FIDO credential registration.</p><p>Roaming authenticators are FIDO authenticators that can be used with different devices (e.g. USB sticks, devices using NFC or Bluetooth).</p><p>Bound authenticators are \"built-in\" FIDO authenticators that cannot be used with different devices (e.g. fingerprint-based in laptop or smartphone).<p>",
      optional = true,
      defaultValue = "ALL"
   )
   @GroupProperty(groupIdentifier = "Registration Settings")
   private FidoAuthenticatorType allowedAuthenticatorType;
   @EnumProperty(
      description = "<p>Tells the FIDO client (browser, REST client) whether user verification is required, preferred, or discouraged during FIDO credential registration.</p><p>\"User verification\" denotes the process by which a FIDO authenticator \"locally\" checks whether the key material may be accessed. Examples: fingerprint, PIN code, touching the authenticator.</p><ul><li>Required: User verification is required for a successful registration.</li><li>Preferred: User verification is preferred but is not required for a successful registration. Whether or not user verification is actually performed depends on the FIDO authenticator.</li><li>Discouraged: User verification should be avoided, but carrying it out will not fail registration. Whether or not user verification is actually performed depends on the FIDO authenticator.</li></ul><p>Note that user verification can be configured separately for authentication.</p>",
      displayName = "User Verification Preference",
      optional = true,
      defaultValue = "PREFERRED"
   )
   @GroupProperty(groupIdentifier = "Registration Settings")
   private FidoUserVerificationPreference registrationUserVerificationPreference;
   @EnumProperty(
      description = "<p>Tells the FIDO client (browser, REST client), what kind of attestation is expected by Airlock IAM.</p><ul><li>Direct: The FIDO client (browser, REST client) must pass the attestation unaltered from the authenticator to Airlock IAM.</li><li>Indirect: The FIDO client (browser, REST client) may replace the attestation from the FIDO authenticator (e.g. for privacy reasons).</li><li>None: Indicates that Airlock IAM is not interested in attestation data and the FIDO client (browser, REST client) may replace the attestation from the FIDO authenticator with a fixed string as defined in the Web Authentication specification.</li></ul><p><b>Security warning</b>: to enforce the type of an attestation, an \"Attestation Verifier\" other than the \"None (FIDO Attestation Verification)\" plugin must be configured. Otherwise, there is no guarantee that the provided attestation is of the desired type.</p>",
      optional = true,
      defaultValue = "DIRECT"
   )
   @GroupProperty(groupIdentifier = "Registration Settings")
   private FidoAttestationType attestationType;
   @PluginProperty(
      description = "<p>Defines how attestations and attestation certificates in particular are used to verify whether a FIDO authenticator is acceptable for registration or not.<p/><p>This can be used, e.g. to restrict the set of allowed FIDO authenticators (i.e. only certain models and/or manufacturers).</p><p>The verification may be disabled by choosing the \"None (FIDO Attestation Verification)\" plugin.</p>",
      displayName = "Attestation Verifier",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Registration Settings")
   private FidoAttestationVerifierConfig attestationVerifier;
   private static final String ATTESTATION_VERIFIER_DISPLAY_NAME = "Attestation Verifier";
   @IntegerProperty(
      description = "<p>Maximum time in seconds a registration process may last.</p><p>The value is both passed to the FIDO client (web browser or REST client) as a hint and used for server-side verification.</p><p>Responses to FIDO registration challenges after the defined timeout are rejected by Airlock IAM.</p>",
      displayName = "Registration Timeout [s]",
      optional = true,
      exampleValues = {60, 300, 600},
      minValue = 0,
      defaultValue = 300
   )
   @GroupProperty(groupIdentifier = "Registration Settings")
   private int registrationTimeout;
   @BooleanProperty(
      description = "<p>If enabled, the description of the AAGUID mapping is used as the display name during the registration flow. If no mapping matches, the display name is set to '-'.</p>\n<p>The auto generated display name can be edited after the Fido Registration Step through a subsequent Fido Credential Display Name Change Step.</p>\n<p>If disabled, the display name must be set explicitly during the registration flow.</p>\n",
      optional = true,
      defaultValue = "false"
   )
   @GroupProperty(groupIdentifier = "Registration Settings")
   private boolean autoGenerateDisplayName;
   @BooleanProperty(
      description = "<p>Limit the creation of multiple credentials for the same account on a single authenticator.</p>\n<ul>\n\t<li>If active, Airock IAM as Relying Party provides all known credentials of this user during registration.\n\tA FIDO client fails if new credentials are created on an authenticator that already has a credential for this user.</li>\n\t<li>If not active, Airock IAM as Relying Party does not provide known credentials of this user during registration.\n\tThe FIDO client creates new credentials on an authenticator even if the authenticator already has a credential for this user.</li>\n</ul>\n<p>Note: For most customers this setting should be active. In some scenarios, e.g., with older Android devices,\ndeactivating this setting mitigates FIDO client limitations.</p>\n",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Registration Settings")
   private boolean preventDoubleRegistration;
   @EnumProperty(
      description = "<p>Tells the FIDO client (browser, REST client) whether user verification is required, preferred, or discouraged during an authentication with a FIDO credential .</p><p>\"User verification\" denotes the process by which a FIDO authenticator \"locally\" checks whether the key material may be accessed. Examples: fingerprint, PIN code, touching the authenticator.</p><ul><li>Required: User verification is required for a successful authentication.</li><li>Preferred: User verification is preferred but is not required for a successful authentication. Whether or not user verification is actually performed depends on the FIDO authenticator.</li><li>Discouraged: User verification should be avoided, but carrying it out will not fail registration. Whether or not user verification is actually performed depends on the FIDO authenticator.</li></ul><p>Note that user verification can be configured separately for registration.</p>",
      displayName = "User Verification Preference",
      optional = true,
      defaultValue = "PREFERRED"
   )
   @GroupProperty(groupIdentifier = "Authentication / Approval Settings")
   private FidoUserVerificationPreference authenticationUserVerificationPreference;
   @IntegerProperty(
      description = "<p>Maximum time in seconds an authentication or approval process may last.</p><p>The value is both passed to the FIDO client (web browser or REST client) as a hint and used for server-side verification.</p><p>Responses to FIDO verification challenges after the defined timeout are rejected by Airlock IAM.</p>",
      displayName = "Authentication / Approval Timeout [s]",
      optional = true,
      exampleValues = {30, 60, 90},
      minValue = 0,
      defaultValue = 60
   )
   @GroupProperty(groupIdentifier = "Authentication / Approval Settings")
   private int authenticationTimeout;
   @StringListProperty(
      description = "<p>Tells the FIDO client (browser, REST client) which transports should be displayed to the user to choose from.\nA successful FIDO authentication is also possible with an empty list of transports.</p>\n<p>This configuration is ignored in FIDO passwordless mode.</p>\n<p>Possible transports:</p>\n<ul>\n\t<li>ble - External Bluetooth Low Energy (BLE) device</li>\n\t<li>nfc - External NFC device</li>\n\t<li>usb - External USB device</li>\n\t<li>internal - e.g. fingerprint sensor</li>\n\t<li>hybrid - combination of transports (e.g. scan QR code and use the fingerprint reader on your phone)</li>\n\t<li>smart-card - ISO/IEC 7816 smart card</li>\n</ul>",
      displayName = "Authentication / Approval Transports",
      unique = true,
      optional = true,
      minElements = 1,
      allowedValues = {"ble", "nfc", "usb", "internal", "hybrid", "smart-card"}
   )
   @GroupProperty(groupIdentifier = "Authentication / Approval Settings")
   private List<String> allowedAuthenticationTransports;
   static final String ALLOWED_AUTHENTICATION_TRANSPORTS = "allowedAuthenticationTransports";
   @StringListProperty(
      description = "<p>Specifies the list of cryptographic algorithms to be used by FIDO authenticators to generate public and private key pairs during registration.</p><p>The order of the algorithms in the list defines the preference (first algorithm is most preferred by Airlock IAM).</p><p><b>Security warning</b>: Usage of RS256 is not recommended for security reasons (see https://tools.ietf.org/html/rfc8812#section-2) and should only be configured for compatibility reasons if required. This is in particular the case if <em>Windows Hello</em> with bound authenticators needs to be supported.</p><ul><li>ES256: ECDSA with SHA-256</li><li>EDDSA: Edwards-curve Digital Signature Algorithm (EdDSA)</li><li>RS256: RSASSA-PKCS1-v1_5 using SHA-256</li></ul>",
      optional = true,
      unique = true,
      minElements = 1,
      defaultValue = {"ES256", "EDDSA"},
      allowedValues = {"ES256", "EDDSA", "RS256"}
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<String> allowedAlgorithms;
   @IntegerProperty(
      description = "Maximum size of an <a href=\"https://www.w3.org/TR/webauthn/#attestation-object\">attestation object</a> in its binary representation in <b>bytes</b> that can be provided by a FIDO credential during registration. Larger attestation objects will fail registration. Changing this value will not impact already registered FIDO credentials. <p><b>Security warning:</b> It is recommended to keep this value as low as possible, especially if the attestation provided during registration is not verified (the plugin \"None (FIDO Attestation Verification)\" is configured), in order to prevent persisting unnecessary data, which could result in a denial of service.</p>",
      defaultValue = 6000,
      exampleValues = {1000, 2000, 6000, 10000},
      minValue = 0,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private int maximumAttestationObjectSizeInBytes;

   @ConfigurationValidator
   static void checkRelyingPartyIdIsValid(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws PropertyConfigurationException {
      String configuredRelyingPartyId = StringPropertyValueReader.effectiveStringConfigValue(config, "relyingPartyId", env, context);
      if (!FidoRelyingPartyId.isValid(configuredRelyingPartyId)) {
         throw new PropertyConstraintViolationException(
            "relyingPartyId", config, env, context, "Relying party ID must be a valid domain: " + LogUtil.quote(configuredRelyingPartyId)
         );
      }
   }

   @ConfigurationValidator
   static void checkRelyingPartyNameIsValid(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws PropertyConfigurationException {
      Optional<String> configuredRelyingPartyName = Optional.ofNullable(
         StringPropertyValueReader.effectiveStringConfigValue(config, "relyingPartyName", env, context)
      );
      if (configuredRelyingPartyName.isPresent() && !FidoRelyingPartyName.isValid(configuredRelyingPartyName.get())) {
         throw new PropertyConstraintViolationException("relyingPartyName", config, env, context, "Relying party name contains invalid characters.");
      }
   }

   public Set<FidoTransport> getAllowedAuthenticationTransports() {
      return this.allowedAuthenticationTransports.stream().map(FidoTransport::fromString).collect(Collectors.toSet());
   }

   public List<SupportedCoseAlgorithmIdentifier> getAllowedAlgorithms() {
      return this.allowedAlgorithms.stream().map(SupportedCoseAlgorithmIdentifier::valueOf).toList();
   }

   public String getRelyingPartyName() {
      return Optional.ofNullable(this.relyingPartyName).orElse(this.relyingPartyId);
   }

   public Duration getRegistrationTimeout() {
      return Duration.ofSeconds(this.registrationTimeout);
   }

   public Duration getAuthenticationTimeout() {
      return Duration.ofSeconds(this.authenticationTimeout);
   }

   public Optional<StringValueProviderConfig> getUserInformationProvider() {
      return Optional.ofNullable(this.userInformationProvider);
   }

   public FidoRepositoryConfig getRepository() {
      return this.repository;
   }

   public String getRelyingPartyId() {
      return this.relyingPartyId;
   }

   public Set<FidoOrigin> getAllOrigins() {
      Set<FidoOrigin> defaultOrigin = Set.of(new FidoUrlOrigin(this.relyingPartyId));
      Set<FidoOrigin> additionalOrigins = this.additionalOrigins.stream().map(FidoAdditionalOriginConfig::getWebAuthnOrigin).collect(Collectors.toSet());
      return Stream.concat(defaultOrigin.stream(), additionalOrigins.stream()).collect(Collectors.toSet());
   }

   public List<FidoAaguidMappingsConfig> getAaguidMappings() {
      return this.aaguidMappings;
   }

   public ResidentKeyRequirement getResidentKey() {
      return ResidentKeyRequirement.valueOf(this.residentKey.toUpperCase(Locale.ROOT));
   }

   public FidoAuthenticatorType getAllowedAuthenticatorType() {
      return this.allowedAuthenticatorType;
   }

   public FidoUserVerificationPreference getRegistrationUserVerificationPreference() {
      return this.registrationUserVerificationPreference;
   }

   public FidoAttestationType getAttestationType() {
      return this.attestationType;
   }

   public FidoAttestationVerifierConfig getAttestationVerifier() {
      return this.attestationVerifier;
   }

   public boolean isAutoGenerateDisplayName() {
      return this.autoGenerateDisplayName;
   }

   public boolean isPreventDoubleRegistration() {
      return this.preventDoubleRegistration;
   }

   public FidoUserVerificationPreference getAuthenticationUserVerificationPreference() {
      return this.authenticationUserVerificationPreference;
   }

   public int getMaximumAttestationObjectSizeInBytes() {
      return this.maximumAttestationObjectSizeInBytes;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public FidoSettingsConfig.FidoSettingsConfigBuilder toBuilder() {
      FidoSettingsConfig.FidoSettingsConfigBuilder builder = new FidoSettingsConfig.FidoSettingsConfigBuilder()
         .repository(this.repository)
         .relyingPartyId(this.relyingPartyId)
         .relyingPartyName(this.relyingPartyName)
         .userInformationProvider(this.userInformationProvider)
         .residentKey(this.residentKey)
         .allowedAuthenticatorType(this.allowedAuthenticatorType)
         .registrationUserVerificationPreference(this.registrationUserVerificationPreference)
         .attestationType(this.attestationType)
         .attestationVerifier(this.attestationVerifier)
         .registrationTimeout(this.registrationTimeout)
         .autoGenerateDisplayName(this.autoGenerateDisplayName)
         .preventDoubleRegistration(this.preventDoubleRegistration)
         .authenticationUserVerificationPreference(this.authenticationUserVerificationPreference)
         .authenticationTimeout(this.authenticationTimeout)
         .allowedAuthenticationTransports(this.allowedAuthenticationTransports)
         .allowedAlgorithms(this.allowedAlgorithms)
         .maximumAttestationObjectSizeInBytes(this.maximumAttestationObjectSizeInBytes);
      if (this.additionalOrigins != null) {
         builder.additionalOrigins(this.additionalOrigins);
      }

      if (this.aaguidMappings != null) {
         builder.aaguidMappings(this.aaguidMappings);
      }

      return builder;
   }

   @SuppressFBWarnings(justification = "generated code")
   public FidoSettingsConfig(
      FidoRepositoryConfig repository,
      String relyingPartyId,
      String relyingPartyName,
      List<FidoAdditionalOriginConfig> additionalOrigins,
      List<FidoAaguidMappingsConfig> aaguidMappings,
      UserInformationProviderConfig userInformationProvider,
      String residentKey,
      FidoAuthenticatorType allowedAuthenticatorType,
      FidoUserVerificationPreference registrationUserVerificationPreference,
      FidoAttestationType attestationType,
      FidoAttestationVerifierConfig attestationVerifier,
      int registrationTimeout,
      boolean autoGenerateDisplayName,
      boolean preventDoubleRegistration,
      FidoUserVerificationPreference authenticationUserVerificationPreference,
      int authenticationTimeout,
      List<String> allowedAuthenticationTransports,
      List<String> allowedAlgorithms,
      int maximumAttestationObjectSizeInBytes
   ) {
      this.repository = repository;
      this.relyingPartyId = relyingPartyId;
      this.relyingPartyName = relyingPartyName;
      this.additionalOrigins = additionalOrigins;
      this.aaguidMappings = aaguidMappings;
      this.userInformationProvider = userInformationProvider;
      this.residentKey = residentKey;
      this.allowedAuthenticatorType = allowedAuthenticatorType;
      this.registrationUserVerificationPreference = registrationUserVerificationPreference;
      this.attestationType = attestationType;
      this.attestationVerifier = attestationVerifier;
      this.registrationTimeout = registrationTimeout;
      this.autoGenerateDisplayName = autoGenerateDisplayName;
      this.preventDoubleRegistration = preventDoubleRegistration;
      this.authenticationUserVerificationPreference = authenticationUserVerificationPreference;
      this.authenticationTimeout = authenticationTimeout;
      this.allowedAuthenticationTransports = allowedAuthenticationTransports;
      this.allowedAlgorithms = allowedAlgorithms;
      this.maximumAttestationObjectSizeInBytes = maximumAttestationObjectSizeInBytes;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class FidoSettingsConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private FidoRepositoryConfig repository;
      @SuppressFBWarnings(justification = "generated code")
      private String relyingPartyId;
      @SuppressFBWarnings(justification = "generated code")
      private String relyingPartyName;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<FidoAdditionalOriginConfig> additionalOrigins;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<FidoAaguidMappingsConfig> aaguidMappings;
      @SuppressFBWarnings(justification = "generated code")
      private UserInformationProviderConfig userInformationProvider;
      @SuppressFBWarnings(justification = "generated code")
      private String residentKey;
      @SuppressFBWarnings(justification = "generated code")
      private FidoAuthenticatorType allowedAuthenticatorType;
      @SuppressFBWarnings(justification = "generated code")
      private FidoUserVerificationPreference registrationUserVerificationPreference;
      @SuppressFBWarnings(justification = "generated code")
      private FidoAttestationType attestationType;
      @SuppressFBWarnings(justification = "generated code")
      private FidoAttestationVerifierConfig attestationVerifier;
      @SuppressFBWarnings(justification = "generated code")
      private int registrationTimeout;
      @SuppressFBWarnings(justification = "generated code")
      private boolean autoGenerateDisplayName;
      @SuppressFBWarnings(justification = "generated code")
      private boolean preventDoubleRegistration;
      @SuppressFBWarnings(justification = "generated code")
      private FidoUserVerificationPreference authenticationUserVerificationPreference;
      @SuppressFBWarnings(justification = "generated code")
      private int authenticationTimeout;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> allowedAuthenticationTransports;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> allowedAlgorithms;
      @SuppressFBWarnings(justification = "generated code")
      private int maximumAttestationObjectSizeInBytes;

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

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder repository(FidoRepositoryConfig repository) {
         this.repository = repository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder relyingPartyId(String relyingPartyId) {
         this.relyingPartyId = relyingPartyId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder relyingPartyName(String relyingPartyName) {
         this.relyingPartyName = relyingPartyName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder additionalOrigin(FidoAdditionalOriginConfig additionalOrigin) {
         if (this.additionalOrigins == null) {
            this.additionalOrigins = new ArrayList<>();
         }

         this.additionalOrigins.add(additionalOrigin);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder additionalOrigins(Collection<? extends FidoAdditionalOriginConfig> additionalOrigins) {
         if (additionalOrigins == null) {
            throw new NullPointerException("additionalOrigins cannot be null");
         }

         if (this.additionalOrigins == null) {
            this.additionalOrigins = new ArrayList<>();
         }

         this.additionalOrigins.addAll(additionalOrigins);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder clearAdditionalOrigins() {
         if (this.additionalOrigins != null) {
            this.additionalOrigins.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder aaguidMapping(FidoAaguidMappingsConfig aaguidMapping) {
         if (this.aaguidMappings == null) {
            this.aaguidMappings = new ArrayList<>();
         }

         this.aaguidMappings.add(aaguidMapping);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder aaguidMappings(Collection<? extends FidoAaguidMappingsConfig> aaguidMappings) {
         if (aaguidMappings == null) {
            throw new NullPointerException("aaguidMappings cannot be null");
         }

         if (this.aaguidMappings == null) {
            this.aaguidMappings = new ArrayList<>();
         }

         this.aaguidMappings.addAll(aaguidMappings);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder clearAaguidMappings() {
         if (this.aaguidMappings != null) {
            this.aaguidMappings.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder userInformationProvider(UserInformationProviderConfig userInformationProvider) {
         this.userInformationProvider = userInformationProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder residentKey(String residentKey) {
         this.residentKey = residentKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder allowedAuthenticatorType(FidoAuthenticatorType allowedAuthenticatorType) {
         this.allowedAuthenticatorType = allowedAuthenticatorType;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder registrationUserVerificationPreference(
         FidoUserVerificationPreference registrationUserVerificationPreference
      ) {
         this.registrationUserVerificationPreference = registrationUserVerificationPreference;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder attestationType(FidoAttestationType attestationType) {
         this.attestationType = attestationType;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder attestationVerifier(FidoAttestationVerifierConfig attestationVerifier) {
         this.attestationVerifier = attestationVerifier;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder registrationTimeout(int registrationTimeout) {
         this.registrationTimeout = registrationTimeout;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder autoGenerateDisplayName(boolean autoGenerateDisplayName) {
         this.autoGenerateDisplayName = autoGenerateDisplayName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder preventDoubleRegistration(boolean preventDoubleRegistration) {
         this.preventDoubleRegistration = preventDoubleRegistration;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder authenticationUserVerificationPreference(
         FidoUserVerificationPreference authenticationUserVerificationPreference
      ) {
         this.authenticationUserVerificationPreference = authenticationUserVerificationPreference;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder authenticationTimeout(int authenticationTimeout) {
         this.authenticationTimeout = authenticationTimeout;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder allowedAuthenticationTransports(List<String> allowedAuthenticationTransports) {
         this.allowedAuthenticationTransports = allowedAuthenticationTransports;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder allowedAlgorithms(List<String> allowedAlgorithms) {
         this.allowedAlgorithms = allowedAlgorithms;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig.FidoSettingsConfigBuilder maximumAttestationObjectSizeInBytes(int maximumAttestationObjectSizeInBytes) {
         this.maximumAttestationObjectSizeInBytes = maximumAttestationObjectSizeInBytes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoSettingsConfig build() {
         List additionalOrigins = switch (this.additionalOrigins == null ? 0 : this.additionalOrigins.size()) {
            case 0 -> Collections.emptyList();
            case 1 -> Collections.singletonList(this.additionalOrigins.get(0));
            default -> Collections.unmodifiableList(new ArrayList<>(this.additionalOrigins));
         };

         return new FidoSettingsConfig(
            this.repository,
            this.relyingPartyId,
            this.relyingPartyName,
            additionalOrigins,
            switch (this.aaguidMappings == null ? 0 : this.aaguidMappings.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.aaguidMappings.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.aaguidMappings));
            },
            this.userInformationProvider,
            this.residentKey,
            this.allowedAuthenticatorType,
            this.registrationUserVerificationPreference,
            this.attestationType,
            this.attestationVerifier,
            this.registrationTimeout,
            this.autoGenerateDisplayName,
            this.preventDoubleRegistration,
            this.authenticationUserVerificationPreference,
            this.authenticationTimeout,
            this.allowedAuthenticationTransports,
            this.allowedAlgorithms,
            this.maximumAttestationObjectSizeInBytes
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "FidoSettingsConfig.FidoSettingsConfigBuilder(repository="
            + this.repository
            + ", relyingPartyId="
            + this.relyingPartyId
            + ", relyingPartyName="
            + this.relyingPartyName
            + ", additionalOrigins="
            + this.additionalOrigins
            + ", aaguidMappings="
            + this.aaguidMappings
            + ", userInformationProvider="
            + this.userInformationProvider
            + ", residentKey="
            + this.residentKey
            + ", allowedAuthenticatorType="
            + this.allowedAuthenticatorType
            + ", registrationUserVerificationPreference="
            + this.registrationUserVerificationPreference
            + ", attestationType="
            + this.attestationType
            + ", attestationVerifier="
            + this.attestationVerifier
            + ", registrationTimeout="
            + this.registrationTimeout
            + ", autoGenerateDisplayName="
            + this.autoGenerateDisplayName
            + ", preventDoubleRegistration="
            + this.preventDoubleRegistration
            + ", authenticationUserVerificationPreference="
            + this.authenticationUserVerificationPreference
            + ", authenticationTimeout="
            + this.authenticationTimeout
            + ", allowedAuthenticationTransports="
            + this.allowedAuthenticationTransports
            + ", allowedAlgorithms="
            + this.allowedAlgorithms
            + ", maximumAttestationObjectSizeInBytes="
            + this.maximumAttestationObjectSizeInBytes
            + ")";
      }
   }
}
