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

import com.airlock.iam.common.api.application.configuration.ConfigurationExtension;
import com.airlock.iam.common.api.application.configuration.credential.RequestAuthenticationConfig;
import com.airlock.iam.common.api.application.configuration.credential.RequestAuthenticationConfigForTransactionApproval;
import com.airlock.iam.common.api.application.configuration.state.StateRepositoryConfig;
import com.airlock.iam.common.api.domain.model.store.user.UserStore;
import com.airlock.iam.common.application.configuration.CorsSettings;
import com.airlock.iam.common.application.configuration.RestConfigSpec;
import com.airlock.iam.common.application.configuration.device.DeviceUsageRepositoryConfig;
import com.airlock.iam.common.application.configuration.gateway.AirlockMicrogatewayConfig;
import com.airlock.iam.common.application.configuration.health.ModuleReadinessConfig;
import com.airlock.iam.common.application.configuration.language.LanguageSettings;
import com.airlock.iam.common.application.configuration.logging.CorrelationIdSettingsConfig;
import com.airlock.iam.common.application.configuration.usertrail.UserTrailLogRepositoryConfig;
import com.airlock.iam.core.application.configuration.store.user.UserStoreProvider;
import com.airlock.iam.core.application.configuration.waf.AirlockGatewayConfig;
import com.airlock.iam.core.application.configuration.waf.GatewayConfig;
import com.airlock.iam.core.misc.authen.UsernameTransformer;
import com.airlock.iam.core.misc.util.context.ContextExtractor;
import com.airlock.iam.core.misc.util.context.NoContextExtractor;
import com.airlock.iam.flow.api.domain.model.flow.FlowId;
import com.airlock.iam.flow.application.configuration.validation.FlowIdConfigValidator;
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.lifecycle.AfterConfigActivation;
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.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
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 com.airlock.iam.plugin.api.exception.PropertyConfigurationException;
import com.airlock.iam.plugin.framework.domain.service.validation.DurationParser;
import com.airlock.iam.plugin.framework.domain.service.validation.DurationValidator;
import com.airlock.iam.plugin.framework.misc.AppId;
import com.airlock.iam.transactionapproval.application.configuration.flow.TransactionApprovalFlowConfig;
import com.google.common.collect.ImmutableList;
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.Optional;

@Plugin(description = "Configures the Transaction Approval web application.", displayName = "Transaction Approval")
@Groups(
   groupSequence = {"Basic Settings", "API Access Control", "Security Settings", "Advanced Settings"},
   openGroups = {"Basic Settings", "API Access Control"}
)
@PluginLicenseRestriction(moduleAffiliation = "TransactionApproval")
public class TransactionApprovalApp implements RestConfigSpec {
   public static final AppId TRANSACTION_APPROVAL_CONFIG_IDENTIFIER = AppId.appId("transaction-approval");
   public static final String TRANSACTION_APPROVAL_DISPLAY_NAME = "Transaction Approval";
   private static final FlowId LEGACY_DEFAULT_FLOW_ID = new FlowId("legacy-default-flow");
   static final String API_ACCESS_CONTROL = "API Access Control";
   @PluginListProperty(description = "The transaction approval flows.", minElements = 1, optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<TransactionApprovalFlowConfig> flows;
   private static final String FLOWS_PROPERTY = "flows";
   @PluginProperty(description = "The user store containing the users that approve transactions.", optional = false, globalPluginReference = "globalUserStore")
   @GroupProperty(groupIdentifier = "Basic Settings")
   private UserStoreProvider userStore;
   @IntegerProperty(
      description = "Maximal number of allowed transaction approval attempts. Failed transaction approval attempts are counted as failed logins for the given credential, i.e., the same counter is used. The user is locked if the number of failed attempts for some credential exceeds this limit. ",
      displayName = "Max Failed Factor Attempts",
      exampleValues = {3, 5},
      defaultValue = 5,
      minValue = 1,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private int maxFailedTransactionApprovals;
   @PluginProperty(
      description = "Settings regarding an Airlock Gateway or Airlock Microgateway reverse proxy placed in front of Airlock IAM.<p>If no settings are configured, extra information from the reverse proxy will not be available and it may be harder to correlate log messages that are written to different log files.</p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private GatewayConfig gatewaySettings;
   @PluginProperty(description = "Determines how a credential is extracted and used to authenticate single requests.", optional = false)
   @GroupProperty(groupIdentifier = "API Access Control")
   private RequestAuthenticationConfigForTransactionApproval requestAuthentication;
   @BooleanProperty(
      description = "<p>If enabled, REST endpoints are protected against CSRF attacks.</p><p>With this protection, the REST API only accepts requests that contain the custom header <tt>X-Same-Domain</tt> with an arbitrary non-empty value. In cross-origin resource sharing (CORS), such requests are not considered <em>simple requests</em> and thus must always be preceded by a preflight request, which prevents cross-site request forgery (CSRF) attacks.</p><p><b>Security warning:</b> Disabling this feature may allow CSRF attacks. Only do so if the REST client is unable to comply with the aforementioned restrictions.</p>",
      optional = true,
      displayName = "CSRF Protection",
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private boolean csrfProtection;
   @IntegerProperty(
      description = "Defines how long it takes (in milliseconds) until IAM answers an 'unsuccessful' request in the transaction approval API. Faster answers are delayed until the configured duration is reached. This helps to avoid timing attacks. Successful or slower responses are not affected by this property. Protection against timing attacks is only provided if IAM is able to process 'unsuccessful' requests within the configured duration.<p>The endpoint that checks a password against the configured policy is excluded from response delays.</p>",
      exampleValues = {500, 1000, 2000},
      defaultValue = 2000,
      minValue = 0,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private int fixedResponseDuration;
   @PluginProperty(
      description = "The settings to allow cross-domain REST calls.",
      optional = true,
      defaultValue = CorsSettings.class,
      displayName = "CORS Settings"
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private CorsSettings corsSettings;
   @PluginProperty(
      description = "Defines where IAM stores all state. As long as only one instance of IAM is running (no horizontal scaling), the in-memory repository can be used.",
      optional = false,
      globalPluginReference = "globalStateRepository"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private StateRepositoryConfig stateRepository;
   @PluginProperty(description = "Configures language settings.", defaultValue = LanguageSettings.class, optional = true)
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private LanguageSettings languageSettings;
   @StringProperty(
      description = "Session idle timeout for the Transaction Approval App. When IAM is deployed behind an Airlock Gateway (WAF), timeout and lifetime values should always be longer than those maintained by the Gateway.",
      displayName = "Session Idle Timeout",
      exampleValues = {"30m", "2h 15m"},
      optional = true,
      defaultValue = "30m"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String sessionIdleTimeout;
   private static final String SESSION_TIMEOUT_PROPERTY = "sessionIdleTimeout";
   private static final String SESSION_TIMEOUT_DISPLAY_NAME = "Session Idle Timeout";
   @StringProperty(
      description = "Session lifetime for the Transaction Approval App. Unlike an idle timeout, the lifetime cannot be extended by activity and is always terminated once the lifetime has been reached. When IAM is deployed behind an Airlock Gateway (WAF), timeout and lifetime values should always be longer than those maintained by the Gateway.",
      displayName = "Session Lifetime",
      exampleValues = {"4h 30m", "8h"},
      optional = true,
      defaultValue = "8h"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String sessionLifetime;
   private static final String SESSION_LIFETIME_PROPERTY = "sessionLifetime";
   private static final String SESSION_LIFETIME_DISPLAY_NAME = "Session Lifetime";
   @PluginProperty(
      description = "Specifies how a context is to be extracted from a request.",
      optional = true,
      defaultValue = NoContextExtractor.class,
      supportsContexts = false
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private ContextExtractor contextExtractor;
   @PluginListProperty(description = "Custom extensions for the REST API.", optional = true, displayName = "Custom Extensions")
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<ConfigurationExtension> customExtensions;
   @PluginProperty(
      description = "Readiness health check endpoint for the Transaction Approval module.",
      optional = true,
      supportsContexts = false,
      defaultValue = ModuleReadinessConfig.class
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private ModuleReadinessConfig readinessHealthCheckEndpoint;
   @PluginProperty(
      description = "<p>Configures the database settings to use when persisting user trail log entries.</p>\n<p>If this value is defined, then all user trail log messages generated by the\nTransaction Approval App module will additionally be forwarded to the database\nconfigured within the referenced repository plugin.</p>\n<p>All forwarded log entries are stored inside the table \"USER_TRAIL_LOG\". Note that\nsetting this value does not disable writing log messages to the Transaction\nApproval App log file.</p>",
      globalPluginReference = "globalUserTrailLogRepository",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private UserTrailLogRepositoryConfig logUserTrailToDatabase;
   @PluginProperty(
      displayName = "Correlation ID Settings",
      description = "<p>Defines settings for correlation ID transfer and logging inside the Transaction Approval module.</p>\n<p>If undefined, no correlation ID will be logged for this module.</p>",
      globalPluginReference = "globalCorrelationIdSettings",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private CorrelationIdSettingsConfig correlationIdSettings;
   @PluginProperty(
      description = "Configures the database settings to use when persisting device usage data. This repository is used by the Device Usage Processor. If not configured, the device usages are not stored and thus no conditions and events based on previous/first device usage can be used.",
      globalPluginReference = "globalDeviceUsageRepository",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private DeviceUsageRepositoryConfig deviceUsageRepositoryConfig;
   private UserStore cachedUserStore;

   public TransactionApprovalApp(
      List<TransactionApprovalFlowConfig> flows,
      UserStoreProvider userStore,
      int maxFailedTransactionApprovals,
      GatewayConfig gatewaySettings,
      RequestAuthenticationConfigForTransactionApproval requestAuthentication,
      boolean csrfProtection,
      int fixedResponseDuration,
      CorsSettings corsSettings,
      StateRepositoryConfig stateRepository,
      LanguageSettings languageSettings,
      String sessionIdleTimeout,
      String sessionLifetime,
      ContextExtractor contextExtractor,
      List<ConfigurationExtension> customExtensions,
      ModuleReadinessConfig readinessHealthCheckEndpoint,
      UserTrailLogRepositoryConfig logUserTrailToDatabase,
      CorrelationIdSettingsConfig correlationIdSettings,
      DeviceUsageRepositoryConfig deviceUsageRepositoryConfig
   ) {
      this.flows = flows;
      this.userStore = userStore;
      this.maxFailedTransactionApprovals = maxFailedTransactionApprovals;
      this.gatewaySettings = gatewaySettings;
      this.requestAuthentication = requestAuthentication;
      this.csrfProtection = csrfProtection;
      this.fixedResponseDuration = fixedResponseDuration;
      this.corsSettings = corsSettings;
      this.stateRepository = stateRepository;
      this.languageSettings = languageSettings;
      this.sessionIdleTimeout = sessionIdleTimeout;
      this.sessionLifetime = sessionLifetime;
      this.contextExtractor = contextExtractor;
      this.customExtensions = customExtensions;
      this.readinessHealthCheckEndpoint = readinessHealthCheckEndpoint;
      this.logUserTrailToDatabase = logUserTrailToDatabase;
      this.correlationIdSettings = correlationIdSettings;
      this.deviceUsageRepositoryConfig = deviceUsageRepositoryConfig;
   }

   @AfterConfigActivation
   void initialize() {
      this.cachedUserStore = this.userStore.getUserStore();
   }

   public Duration getSessionIdleTimeout() {
      return new DurationParser(this.sessionIdleTimeout).getDuration();
   }

   public Duration getSessionLifetime() {
      return new DurationParser(this.sessionLifetime).getDuration();
   }

   public List<UsernameTransformer> getUsernameTransformers() {
      return ImmutableList.of();
   }

   public Optional<AirlockGatewayConfig> getGatewayConfig() {
      return Optional.ofNullable(this.gatewaySettings).filter(AirlockGatewayConfig.class::isInstance).map(AirlockGatewayConfig.class::cast);
   }

   public Optional<AirlockMicrogatewayConfig> getMicrogatewayConfig() {
      return Optional.ofNullable(this.gatewaySettings).filter(AirlockMicrogatewayConfig.class::isInstance).map(AirlockMicrogatewayConfig.class::cast);
   }

   public Optional<CorrelationIdSettingsConfig> getCorrelationIdSettingsConfig() {
      return Optional.ofNullable(this.correlationIdSettings);
   }

   public Optional<String> getBaseUri() {
      return Optional.empty();
   }

   public boolean isLinkResponseRewritingEnabled() {
      return false;
   }

   public boolean isCsrfProtected() {
      return this.csrfProtection;
   }

   public Optional<RequestAuthenticationConfig> getRequestAuthenticationConfig() {
      return Optional.of(this.requestAuthentication);
   }

   public Optional<UserTrailLogRepositoryConfig> getUserTrailLogRepositoryConfig() {
      return Optional.ofNullable(this.logUserTrailToDatabase);
   }

   public Optional<DeviceUsageRepositoryConfig> getDeviceUsageRepositoryConfig() {
      return Optional.ofNullable(this.deviceUsageRepositoryConfig);
   }

   public Optional<TransactionApprovalFlowConfig> getDefaultFlow() {
      return this.flows.stream().filter(flow -> flow.getFlowId().equals(LEGACY_DEFAULT_FLOW_ID)).findFirst();
   }

   @ConfigurationValidator
   static void validateFlowIdsUnique(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws PropertyConfigurationException {
      new FlowIdConfigValidator(pc, env, context, "flows", "flowId").validateFlowIdsUniqueForFlows();
   }

   @ConfigurationValidator
   public static void validateSessionDurations(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws ConfigurationException {
      DurationValidator.validateDurationField1IsLargerOrEqualField2IfBothSet(
         config, "sessionLifetime", "Session Lifetime", "sessionIdleTimeout", "Session Idle Timeout", env, context
      );
      DurationValidator.validateDurationMinValueIfSet(config, "sessionIdleTimeout", Duration.ofSeconds(10L), env, context);
      DurationValidator.validateDurationMinValueIfSet(config, "sessionLifetime", Duration.ofSeconds(10L), env, context);
   }

   public List<TransactionApprovalFlowConfig> getFlows() {
      return this.flows;
   }

   public int getMaxFailedTransactionApprovals() {
      return this.maxFailedTransactionApprovals;
   }

   public int getFixedResponseDuration() {
      return this.fixedResponseDuration;
   }

   public CorsSettings getCorsSettings() {
      return this.corsSettings;
   }

   public StateRepositoryConfig getStateRepository() {
      return this.stateRepository;
   }

   public LanguageSettings getLanguageSettings() {
      return this.languageSettings;
   }

   public ContextExtractor getContextExtractor() {
      return this.contextExtractor;
   }

   public List<ConfigurationExtension> getCustomExtensions() {
      return this.customExtensions;
   }

   public ModuleReadinessConfig getReadinessHealthCheckEndpoint() {
      return this.readinessHealthCheckEndpoint;
   }

   public UserStore getCachedUserStore() {
      return this.cachedUserStore;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class TransactionApprovalAppBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<TransactionApprovalFlowConfig> flows;
      @SuppressFBWarnings(justification = "generated code")
      private UserStoreProvider userStore;
      @SuppressFBWarnings(justification = "generated code")
      private int maxFailedTransactionApprovals;
      @SuppressFBWarnings(justification = "generated code")
      private GatewayConfig gatewaySettings;
      @SuppressFBWarnings(justification = "generated code")
      private RequestAuthenticationConfigForTransactionApproval requestAuthentication;
      @SuppressFBWarnings(justification = "generated code")
      private boolean csrfProtection;
      @SuppressFBWarnings(justification = "generated code")
      private int fixedResponseDuration;
      @SuppressFBWarnings(justification = "generated code")
      private CorsSettings corsSettings;
      @SuppressFBWarnings(justification = "generated code")
      private StateRepositoryConfig stateRepository;
      @SuppressFBWarnings(justification = "generated code")
      private LanguageSettings languageSettings;
      @SuppressFBWarnings(justification = "generated code")
      private String sessionIdleTimeout;
      @SuppressFBWarnings(justification = "generated code")
      private String sessionLifetime;
      @SuppressFBWarnings(justification = "generated code")
      private ContextExtractor contextExtractor;
      @SuppressFBWarnings(justification = "generated code")
      private List<ConfigurationExtension> customExtensions;
      @SuppressFBWarnings(justification = "generated code")
      private ModuleReadinessConfig readinessHealthCheckEndpoint;
      @SuppressFBWarnings(justification = "generated code")
      private UserTrailLogRepositoryConfig logUserTrailToDatabase;
      @SuppressFBWarnings(justification = "generated code")
      private CorrelationIdSettingsConfig correlationIdSettings;
      @SuppressFBWarnings(justification = "generated code")
      private DeviceUsageRepositoryConfig deviceUsageRepositoryConfig;

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

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder flow(TransactionApprovalFlowConfig flow) {
         if (this.flows == null) {
            this.flows = new ArrayList<>();
         }

         this.flows.add(flow);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder flows(Collection<? extends TransactionApprovalFlowConfig> flows) {
         if (flows == null) {
            throw new NullPointerException("flows cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder clearFlows() {
         if (this.flows != null) {
            this.flows.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder userStore(UserStoreProvider userStore) {
         this.userStore = userStore;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder maxFailedTransactionApprovals(int maxFailedTransactionApprovals) {
         this.maxFailedTransactionApprovals = maxFailedTransactionApprovals;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder gatewaySettings(GatewayConfig gatewaySettings) {
         this.gatewaySettings = gatewaySettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder requestAuthentication(RequestAuthenticationConfigForTransactionApproval requestAuthentication) {
         this.requestAuthentication = requestAuthentication;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder csrfProtection(boolean csrfProtection) {
         this.csrfProtection = csrfProtection;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder fixedResponseDuration(int fixedResponseDuration) {
         this.fixedResponseDuration = fixedResponseDuration;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder corsSettings(CorsSettings corsSettings) {
         this.corsSettings = corsSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder stateRepository(StateRepositoryConfig stateRepository) {
         this.stateRepository = stateRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder languageSettings(LanguageSettings languageSettings) {
         this.languageSettings = languageSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder sessionIdleTimeout(String sessionIdleTimeout) {
         this.sessionIdleTimeout = sessionIdleTimeout;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder sessionLifetime(String sessionLifetime) {
         this.sessionLifetime = sessionLifetime;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder contextExtractor(ContextExtractor contextExtractor) {
         this.contextExtractor = contextExtractor;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder customExtensions(List<ConfigurationExtension> customExtensions) {
         this.customExtensions = customExtensions;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder readinessHealthCheckEndpoint(ModuleReadinessConfig readinessHealthCheckEndpoint) {
         this.readinessHealthCheckEndpoint = readinessHealthCheckEndpoint;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder logUserTrailToDatabase(UserTrailLogRepositoryConfig logUserTrailToDatabase) {
         this.logUserTrailToDatabase = logUserTrailToDatabase;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder correlationIdSettings(CorrelationIdSettingsConfig correlationIdSettings) {
         this.correlationIdSettings = correlationIdSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp.TransactionApprovalAppBuilder deviceUsageRepositoryConfig(DeviceUsageRepositoryConfig deviceUsageRepositoryConfig) {
         this.deviceUsageRepositoryConfig = deviceUsageRepositoryConfig;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalApp build() {
         return new TransactionApprovalApp(
            switch (this.flows == null ? 0 : this.flows.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.flows.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.flows));
            },
            this.userStore,
            this.maxFailedTransactionApprovals,
            this.gatewaySettings,
            this.requestAuthentication,
            this.csrfProtection,
            this.fixedResponseDuration,
            this.corsSettings,
            this.stateRepository,
            this.languageSettings,
            this.sessionIdleTimeout,
            this.sessionLifetime,
            this.contextExtractor,
            this.customExtensions,
            this.readinessHealthCheckEndpoint,
            this.logUserTrailToDatabase,
            this.correlationIdSettings,
            this.deviceUsageRepositoryConfig
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "TransactionApprovalApp.TransactionApprovalAppBuilder(flows="
            + this.flows
            + ", userStore="
            + this.userStore
            + ", maxFailedTransactionApprovals="
            + this.maxFailedTransactionApprovals
            + ", gatewaySettings="
            + this.gatewaySettings
            + ", requestAuthentication="
            + this.requestAuthentication
            + ", csrfProtection="
            + this.csrfProtection
            + ", fixedResponseDuration="
            + this.fixedResponseDuration
            + ", corsSettings="
            + this.corsSettings
            + ", stateRepository="
            + this.stateRepository
            + ", languageSettings="
            + this.languageSettings
            + ", sessionIdleTimeout="
            + this.sessionIdleTimeout
            + ", sessionLifetime="
            + this.sessionLifetime
            + ", contextExtractor="
            + this.contextExtractor
            + ", customExtensions="
            + this.customExtensions
            + ", readinessHealthCheckEndpoint="
            + this.readinessHealthCheckEndpoint
            + ", logUserTrailToDatabase="
            + this.logUserTrailToDatabase
            + ", correlationIdSettings="
            + this.correlationIdSettings
            + ", deviceUsageRepositoryConfig="
            + this.deviceUsageRepositoryConfig
            + ")";
      }
   }
}
