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

import com.airlock.iam.admin.application.configuration.administrators.AdministratorsConfiguration;
import com.airlock.iam.admin.application.configuration.analytics.LicenseAnalyticsConfig;
import com.airlock.iam.admin.application.configuration.csp.AdminappContentSecurityPolicyConfig;
import com.airlock.iam.admin.application.configuration.csp.ContentSecurityPolicyConfig;
import com.airlock.iam.admin.application.configuration.event.AdminappEventSettingsConfig;
import com.airlock.iam.admin.application.configuration.logviewer.LogViewer;
import com.airlock.iam.admin.application.configuration.maintenancemessages.MaintenanceMessageConfiguration;
import com.airlock.iam.admin.application.configuration.realm.RealmAdministrationConfig;
import com.airlock.iam.admin.application.configuration.techclients.TechClientsConfig;
import com.airlock.iam.admin.application.configuration.tokens.TokensConfiguration;
import com.airlock.iam.admin.application.configuration.users.UsersConfiguration;
import com.airlock.iam.common.api.application.configuration.ConfigurationExtension;
import com.airlock.iam.common.api.application.configuration.state.StateRepositoryConfig;
import com.airlock.iam.common.application.configuration.gateway.AirlockMicrogatewayConfig;
import com.airlock.iam.common.application.configuration.logging.CorrelationIdSettingsConfig;
import com.airlock.iam.common.application.configuration.state.IamSessionSameSitePolicy;
import com.airlock.iam.common.application.configuration.usertrail.UserTrailLogRepositoryConfig;
import com.airlock.iam.core.application.configuration.waf.AirlockGatewayConfig;
import com.airlock.iam.core.application.configuration.waf.GatewayConfig;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
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.PluginPropertyLicenseRestriction;
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.EnumProperty;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
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.RegexProperty;
import com.airlock.iam.plugin.api.annotation.property.StringListProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.exception.PropertyConfigurationException;
import com.airlock.iam.plugin.api.valuereader.IntegerPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
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.plugin.framework.misc.ioc.PluginLoader;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;

@Plugin(description = "Configures the Adminapp module used to administrate users, credentials, and messages.", displayName = "Adminapp")
@Groups(groupSequence = {"Basic Settings", "SSO Settings", "Advanced Settings"}, openGroups = "Basic Settings")
public class Adminapp {
   public static final String ADMINAPP_DISPLAY_NAME = "Adminapp";
   static final String SSO_SETTINGS_GROUP = "SSO Settings";
   public static final String ADMINAPP_PLUGIN_IDENTIFIER = "adminapp";
   public static final AppId ADMINAPP_CONFIG_IDENTIFIER = AppId.appId("adminapp");
   private static final ELogger LOG = ELoggerFactory.getLogger(Adminapp.class);
   private static final String LICENSE_ANALYTICS_DISPLAY_NAME = "License and Usage Analytics";
   @StringListProperty(
      description = "The admin application page to be displayed after login. If more than one start page is configured, the system displays the first page for which the current user is authorized. If none is found, the system displays an empty page.",
      displayName = "Start Pages",
      optional = true,
      defaultValue = {"viewLog", "listUsers", "manageTokens"},
      allowedValues = {
            "listUsers",
            "listMaintenanceMessages",
            "viewLog",
            "listAdministrators",
            "manageTokens",
            "license",
            "editConfig",
            "accessServiceContainer",
            "viewTechnicalClients"
      },
      exampleElements = {"viewLog", "listUsers"}
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<String> startPages;
   public static final String START_PAGES_DISPLAY_NAME = "Start Pages";
   @PluginProperty(
      description = "Defines settings related to users. This includes <ul> <li>Authentication of users</li> <li>Management of user credentials and tokens</li> <li>Management of users</li></ul>If not provided, the users management is disabled.",
      optional = true,
      displayName = "Users"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private UsersConfiguration users;
   public static final String USERS_PROPERTY = "users";
   public static final String USERS_DISPLAY_NAME = "Users";
   @PluginProperty(description = "Defines access control in the Adminapp and to the Adminapp REST API.", optional = false, displayName = "Access Control")
   @GroupProperty(groupIdentifier = "Basic Settings")
   private AdminappAccessControllerConfig accessControl;
   public static final String ACCESS_CONTROL_DISPLAY_NAME = "Access Control";
   @PluginProperty(description = "Configures the maintenance message facility of the Adminapp.", optional = true)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private MaintenanceMessageConfiguration maintenanceMessages;
   @PluginProperty(
      description = "Defines settings related to administrators. This includes <ul> <li>Authentication of administrators</li> <li>Authorization of administrators</li> <li>Management of administrators (optional)</li></ul>",
      optional = false,
      displayName = "Administrators"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private AdministratorsConfiguration administrators;
   public static final String ADMINISTRATORS_DISPLAY_NAME = "Administrators";
   @PluginProperty(description = "Defines settings related to tokens. This includes <ul> <li>Management of tokens</li></ul>", optional = true)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private TokensConfiguration tokens;
   public static final String TOKENS_PROPERTY = "tokens";
   @PluginProperty(description = "Defines settings related to technical clients.", optional = true)
   @GroupProperty(groupIdentifier = "Basic Settings")
   @PluginPropertyLicenseRestriction(moduleAffiliation = "TechClients")
   private TechClientsConfig technicalClients;
   @PluginProperty(
      description = "Enables REST services for the Adminapp.",
      optional = true,
      defaultValue = DenyingAdminappRestConfig.class,
      displayName = "REST API Configuration"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private AdminappRestConfigSpec rest;
   public static final String REST_API_CONFIGURATION_DISPLAY_NAME = "REST API Configuration";
   public static final String REST_PROPERTY = "rest";
   @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 = "Configures handling of events in the Adminapp.", optional = true)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private AdminappEventSettingsConfig eventSettings;
   public static final String EVENT_SETTINGS_DISPLAY_NAME = "Event Settings";
   @StringProperty(
      description = "The service container secret is used to access the service container from the Adminapp. The shared secret will be used to encrypt the SSO ticket, sent from the Adminapp to the service container in order to authenticate the admin. The shared secret must be identical to the property <b>Service Container Shared Secret</b> within Service Container (Advanced Settings). When not configured, no Service Container link will be displayed in Adminapp.",
      optional = true,
      displayName = "Service Container Shared Secret",
      isSensitive = true
   )
   @GroupProperty(groupIdentifier = "SSO Settings")
   private String serviceContainerSharedSecret;
   public static final String SERVICE_CONTAINER_SHARED_SECRET_DISPLAY_NAME = "Service Container Shared Secret";
   @PluginProperty(description = "Configuration of the Log Viewer.", optional = true, displayName = "Log Viewer")
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private LogViewer logViewer;
   public static final String LOG_VIEWER_DISPLAY_NAME = "Log Viewer";
   @PluginProperty(
      description = "<p>Enables realm administration. This feature limits the rights of administrators to a single realm. All users that a realm administrator creates are assigned to his realm and he can only administer users of his own realm.</p>The assignment of a realm to an administrator requires super administrator authorization.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   @PluginPropertyLicenseRestriction(moduleAffiliation = "RealmAdministration")
   private RealmAdministrationConfig realmAdministration;
   @StringProperty(
      description = "Session idle timeout for the Adminapp (including Config Editor).\nWhen IAM is deployed behind an Airlock Gateway (WAF), timeout and lifetime values should always\nbe 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";
   public static final String SESSION_TIMEOUT_DISPLAY_NAME = "Session Idle Timeout";
   @StringProperty(
      description = "Session lifetime for the Adminapp (but not for the Config Editor).\nUnlike an idle timeout, the lifetime cannot be extended by activity and is always terminated once the lifetime has been reached.\nWhen IAM is deployed behind an Airlock Gateway (WAF), timeout and lifetime values should always\nbe 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";
   public static final String SESSION_LIFETIME_PLUGIN_DOC = "Session lifetime for the Adminapp (but not for the Config Editor).\nUnlike an idle timeout, the lifetime cannot be extended by activity and is always terminated once the lifetime has been reached.\nWhen IAM is deployed behind an Airlock Gateway (WAF), timeout and lifetime values should always\nbe longer than those maintained by the Gateway.";
   @EnumProperty(
      displayName = "Session Cookie SameSite Policy",
      description = "<p>Specifies the 'SameSite' cookie attribute of the IAM session cookie 'iam-session-id'. The 'Secure' attribute is automatically set based on whether the request was performed using http or https (see exception for 'None' below).</p><ul><li>Strict: The cookie is not sent in cross-origin requests.</li><li>Lax: The cookie is sent in some cross-origin requests, such as GET requests.</li><li>None: The cookie is sent in cross-origin requests. In this case, the 'Secure' Cookie-Attribute is always set, regardless of whether the request was performed using http or https.Use this setting when using SAML2 in combination with cross-domain POST Bindings.</li><li>No SameSite Attribute: No attribute is set. Browsers apply their default behaviour, usually 'Lax'.</li></ul>",
      defaultValue = "LAX",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private IamSessionSameSitePolicy sameSitePolicy;
   @PluginProperty(
      description = "Configures language settings.<br/>If not set, the default language is German and the allowed languages are German, English and French.",
      displayName = "Language Settings",
      optional = true,
      defaultValue = AdminappLanguageSettings.class
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private AdminappLanguageSettings languageSettings;
   public static final String LANGUAGE_SETTINGS_DISPLAY_NAME = "Language Settings";
   @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;
   @StringProperty(
      description = "<p>The page displayed instead of the default login page. This can be used if authentication is done by an external service.</p>\n<p>This value must not be URL encoded. Only URLs starting with \"https://\" or \"http://\" are\ntreated as absolute URLs, otherwise the redirect is relative. Furthermore, if the URL starts with\n\"app/\" or \"/app/\" the redirect is performed within the Adminapp UI (context path not needed).</p>\n<p>This property can be overridden by the <tt>loginUrl</tt> URL parameter (see Allowed Login URL Pattern).</p>",
      optional = true,
      exampleValues = {"https://another.server.com/login", "app/login", "/mycustomresource/login"},
      displayName = "Custom Login URL"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String customLoginUrl;
   static final String CUSTOM_LOGIN_URL_FIELD = "customLoginUrl";
   public static final String CUSTOM_LOGIN_URL_DISPLAY_NAME = "Custom Login URL";
   @StringProperty(
      description = "<p>The forward page displayed after the logout if no location parameter is set.</p>\n<p>This value must not be URL encoded. Only URLs starting with \"https://\" or \"http://\" are\ntreated as absolute URLs, otherwise the redirect is relative. Furthermore, if the URL starts with\n\"app/\" or \"/app/\" the redirect is performed within the Adminapp UI (context path not needed).</p>\n<p>This property can be overridden by the <tt>afterLogout</tt> URL parameter (see Allowed After Logout URL Pattern).</p>",
      optional = true,
      defaultValue = "app/login",
      exampleValues = {"https://another.server.com/logout-disclaimer", "app/login", "/mycustomresource/logout-disclaimer"},
      displayName = "After Logout URL"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String afterLogoutUrl;
   public static final String AFTER_LOGOUT_URL_DISPLAY_NAME = "After Logout URL";
   @RegexProperty(
      description = "<p>A regular expression describing the Login URLs that are allowed to be sent to IAM in the\n<tt>loginUrl</tt> URL parameter.</p>\n<p>A matching URL will be used to redirect users who have not yet authenticated or whose session has expired.\nIf no pattern is configured, no URL will match. Matching URLs will have precedence over the URL configured in\nCustom Login URL.</p>\n",
      optional = true,
      exampleValues = {"https?://login\\.airlock\\.com/", "https://[a-z0-9]+\\.airlock\\.com/login", "https://login\\.airlock\\.com/\\?username=.*"},
      displayName = "Allowed Login URL Pattern"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private Pattern allowedLoginUrlPattern;
   @RegexProperty(
      description = "<p>A regular expression describing the Logout URLs that are allowed to be sent to IAM in the\n<tt>afterLogoutUrl</tt> URL parameter.</p>\n<p>A matching URL will be used to redirect the user after a successful logout in IAM.\nIf no pattern is configured, no URL will match. Matching URLs will have precedence over the URL configured in\nAfter Logout URL.</p>\n",
      optional = true,
      exampleValues = {"https?://logout\\.airlock\\.com/", "https://[a-z0-9]+\\.airlock\\.com/logout", "https://logout\\.airlock\\.com/\\?username=.*"},
      displayName = "Allowed After Logout URL Pattern"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private Pattern allowedAfterLogoutUrlPattern;
   @StringProperty(
      displayName = "Skin Color",
      optional = true,
      defaultValue = "blue",
      description = "The skin of the Adminapp. This configuration may be overridden by the <tt>skin</tt> URL parameter, if the property \"Allow Skin URI Parameter\" is enabled.",
      allowedValues = {"blue", "green", "red", "orange", "violet", "purple", "grey", "black"}
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String skin;
   public static final String SKIN_COLOR_DISPLAY_NAME = "Skin Color";
   @BooleanProperty(
      description = "Enables overriding the Adminapp skin with the <tt>skin</tt> URL parameter.",
      optional = true,
      defaultValue = "false",
      displayName = "Allow Skin URL Parameter"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private boolean skinFromParamAllowed;
   @StringProperty(
      displayName = "Custom Instance Tag",
      optional = true,
      description = "Labels the instance with a custom tag that is displayed in the Adminapp. This can be used in combination with the 'Skin Color' property to visually identify an instance."
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String instanceTag;
   public static final String CUSTOM_INSTANCE_TAG_DISPLAY_NAME = "Custom Instance Tag";
   @PluginProperty(
      description = "Content Security Policy (CSP) for the Adminapp.\n<p>Neither the Config Editor nor the Service Container are covered by this CSP.</p>",
      displayName = "Content Security Policy (CSP)",
      defaultValue = AdminappContentSecurityPolicyConfig.class,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private ContentSecurityPolicyConfig contentSecurityPolicy;
   @PluginProperty(
      description = "<p>Airlock IAM always collects and transmits license analytics data, as per our terms and conditions.</p>\n<p>In this property, you may enable additional usage data collection to help improve Airlock IAM.</p>",
      displayName = "License and Usage Analytics",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private LicenseAnalyticsConfig licenseAnalytics;
   @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 Adminapp module\nwill additionally be forwarded to the database configured within the referenced repository plugin.</p>\n<p>All forwarded log entries are stored inside the table \"USER_TRAIL_LOG\". Note that setting this\nvalue does not disable writing log messages to the Adminapp 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 Adminapp 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;
   @PluginListProperty(description = "Custom extensions for the Adminapp.", optional = true)
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<ConfigurationExtension> customExtensions;

   public Optional<AdminappEventSettingsConfig> getEventSettings() {
      return Optional.ofNullable(this.eventSettings);
   }

   public Optional<AdminappRestConfigSpec> getRestConfig() {
      return Optional.ofNullable(this.rest);
   }

   public MaintenanceMessageConfiguration getMaintenanceMessageConfiguration() {
      return this.maintenanceMessages;
   }

   public AdministratorsConfiguration getAdministratorsConfiguration() {
      return this.administrators;
   }

   public Optional<UsersConfiguration> getUsersConfiguration() {
      return Optional.ofNullable(this.users);
   }

   public Optional<TokensConfiguration> getTokensConfiguration() {
      return Optional.ofNullable(this.tokens);
   }

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

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

   public static Adminapp get() {
      return Configuration.getInstance().getAdminapp();
   }

   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<RealmAdministrationConfig> getRealmAdministration() {
      return Optional.ofNullable(this.realmAdministration);
   }

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

   public List<String> getStartPages() {
      return this.startPages;
   }

   public AdminappAccessControllerConfig getAccessControl() {
      return this.accessControl;
   }

   @Nullable
   public LogViewer getLogViewer() {
      return this.logViewer;
   }

   public IamSessionSameSitePolicy getSameSitePolicy() {
      return this.sameSitePolicy;
   }

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

   @Nullable
   public String getServiceContainerSharedSecret() {
      return this.serviceContainerSharedSecret;
   }

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

   public String getAfterLogoutUrl() {
      return this.afterLogoutUrl;
   }

   public String getSkin() {
      return this.skin;
   }

   public boolean isSkinFromParamAllowed() {
      return this.skinFromParamAllowed;
   }

   public ContentSecurityPolicyConfig getContentSecurityPolicy() {
      return this.contentSecurityPolicy;
   }

   public Optional<LicenseAnalyticsConfig> getLicenseAnalytics() {
      return Optional.ofNullable(this.licenseAnalytics);
   }

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

   public boolean isInRecoveryMode() {
      return Configuration.isUnlicensed();
   }

   @AfterConfigActivation
   void afterConfigActivation() {
      this.disableAllComponentsIfInRecoveryMode();
      this.logLicenseAnalyticConfiguration();
   }

   private void disableAllComponentsIfInRecoveryMode() {
      if (this.isInRecoveryMode()) {
         PluginLoader.unloadPluginTree(this.users);
         this.users = null;
         PluginLoader.unloadPluginTree(this.logViewer);
         this.logViewer = null;
         PluginLoader.unloadPluginTree(this.maintenanceMessages);
         this.maintenanceMessages = null;
         PluginLoader.unloadPluginTree(this.tokens);
         this.tokens = null;
         this.serviceContainerSharedSecret = null;
      }

      this.accessControl.setRecoveryMode(this.isInRecoveryMode());
   }

   private void logLicenseAnalyticConfiguration() {
      if (!this.licenseAnalytics.isAnalyticsPermitted()) {
         LOG.info("Analytics is disabled because the active license does not permit it.");
      } else if (this.licenseAnalytics.isEnableUsageAnalytics()) {
         LOG.info(String.format("Plugin '%s' is configured to transmit License and Usage Analytics.", "License and Usage Analytics"));
      } else {
         LOG.info(String.format("Plugin '%s' is configured to only transmit License Analytics. No Usage Analytics are sent.", "License and Usage Analytics"));
      }
   }

   @ConfigurationValidator
   static void validateAdminapp(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment) throws PropertyConfigurationException {
      AdminappConfigValidator.checkIfVascoOtpTokenManagerRequired(config, env, context);
      AdminappConfigValidator.checkCustomLoginUrl(config, env, context);
   }

   @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 Optional<TechClientsConfig> getTechnicalClients() {
      return Optional.ofNullable(this.technicalClients);
   }

   public Optional<String> getCustomLoginUrl() {
      return Optional.ofNullable(this.customLoginUrl);
   }

   public Optional<Pattern> getAllowedLoginUrlPattern() {
      return Optional.ofNullable(this.allowedLoginUrlPattern);
   }

   public Optional<Pattern> getAllowedAfterLogoutUrlPattern() {
      return Optional.ofNullable(this.allowedAfterLogoutUrlPattern);
   }

   public Optional<String> getInstanceTag() {
      return Optional.ofNullable(this.instanceTag);
   }

   @ConfigurationValidator
   public static void validateMaxUsersToList(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws ConfigurationException {
      PluginConfiguration usersConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "users", env, context);
      if (usersConfig != null) {
         Integer maxUsersToList = IntegerPropertyValueReader.effectiveIntegerConfigValue(usersConfig, "maxUsersToList", env, context);
         Preconditions.checkNotNull(maxUsersToList, "expected to not be null since it has a default value");
         int maxPageSize = effectiveMaxPageSize(config, env, context);
         if (maxUsersToList > maxPageSize) {
            throw new PropertyConfigurationException(
               usersConfig,
               env,
               context,
               String.format(
                  "Max Users To List must not be greater than the general page size limit of REST resources (currently %d, see 'Max Page Size' in the 'REST API Configuration')",
                  maxPageSize
               ),
               new String[]{"maxUsersToList"}
            );
         }
      }
   }

   private static int effectiveMaxPageSize(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws ConfigurationException {
      PluginConfiguration restApiConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "rest", env, context);
      return restApiConfig != null && restApiConfig.isOfPluginClass(AdminappRestConfig.class)
         ? (Integer)Preconditions.checkNotNull(
            IntegerPropertyValueReader.effectiveIntegerConfigValue(restApiConfig, "maxPageSize", env, context),
            "expected to not be null since it has a default value"
         )
         : 5000;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public Adminapp(
      List<String> startPages,
      UsersConfiguration users,
      AdminappAccessControllerConfig accessControl,
      MaintenanceMessageConfiguration maintenanceMessages,
      AdministratorsConfiguration administrators,
      TokensConfiguration tokens,
      TechClientsConfig technicalClients,
      AdminappRestConfigSpec rest,
      GatewayConfig gatewaySettings,
      AdminappEventSettingsConfig eventSettings,
      String serviceContainerSharedSecret,
      LogViewer logViewer,
      RealmAdministrationConfig realmAdministration,
      String sessionIdleTimeout,
      String sessionLifetime,
      IamSessionSameSitePolicy sameSitePolicy,
      AdminappLanguageSettings languageSettings,
      StateRepositoryConfig stateRepository,
      String customLoginUrl,
      String afterLogoutUrl,
      Pattern allowedLoginUrlPattern,
      Pattern allowedAfterLogoutUrlPattern,
      String skin,
      boolean skinFromParamAllowed,
      String instanceTag,
      ContentSecurityPolicyConfig contentSecurityPolicy,
      LicenseAnalyticsConfig licenseAnalytics,
      UserTrailLogRepositoryConfig logUserTrailToDatabase,
      CorrelationIdSettingsConfig correlationIdSettings,
      List<ConfigurationExtension> customExtensions
   ) {
      this.startPages = startPages;
      this.users = users;
      this.accessControl = accessControl;
      this.maintenanceMessages = maintenanceMessages;
      this.administrators = administrators;
      this.tokens = tokens;
      this.technicalClients = technicalClients;
      this.rest = rest;
      this.gatewaySettings = gatewaySettings;
      this.eventSettings = eventSettings;
      this.serviceContainerSharedSecret = serviceContainerSharedSecret;
      this.logViewer = logViewer;
      this.realmAdministration = realmAdministration;
      this.sessionIdleTimeout = sessionIdleTimeout;
      this.sessionLifetime = sessionLifetime;
      this.sameSitePolicy = sameSitePolicy;
      this.languageSettings = languageSettings;
      this.stateRepository = stateRepository;
      this.customLoginUrl = customLoginUrl;
      this.afterLogoutUrl = afterLogoutUrl;
      this.allowedLoginUrlPattern = allowedLoginUrlPattern;
      this.allowedAfterLogoutUrlPattern = allowedAfterLogoutUrlPattern;
      this.skin = skin;
      this.skinFromParamAllowed = skinFromParamAllowed;
      this.instanceTag = instanceTag;
      this.contentSecurityPolicy = contentSecurityPolicy;
      this.licenseAnalytics = licenseAnalytics;
      this.logUserTrailToDatabase = logUserTrailToDatabase;
      this.correlationIdSettings = correlationIdSettings;
      this.customExtensions = customExtensions;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class AdminappBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private List<String> startPages;
      @SuppressFBWarnings(justification = "generated code")
      private UsersConfiguration users;
      @SuppressFBWarnings(justification = "generated code")
      private AdminappAccessControllerConfig accessControl;
      @SuppressFBWarnings(justification = "generated code")
      private MaintenanceMessageConfiguration maintenanceMessages;
      @SuppressFBWarnings(justification = "generated code")
      private AdministratorsConfiguration administrators;
      @SuppressFBWarnings(justification = "generated code")
      private TokensConfiguration tokens;
      @SuppressFBWarnings(justification = "generated code")
      private TechClientsConfig technicalClients;
      @SuppressFBWarnings(justification = "generated code")
      private AdminappRestConfigSpec rest;
      @SuppressFBWarnings(justification = "generated code")
      private GatewayConfig gatewaySettings;
      @SuppressFBWarnings(justification = "generated code")
      private AdminappEventSettingsConfig eventSettings;
      @SuppressFBWarnings(justification = "generated code")
      private String serviceContainerSharedSecret;
      @SuppressFBWarnings(justification = "generated code")
      private LogViewer logViewer;
      @SuppressFBWarnings(justification = "generated code")
      private RealmAdministrationConfig realmAdministration;
      @SuppressFBWarnings(justification = "generated code")
      private String sessionIdleTimeout;
      @SuppressFBWarnings(justification = "generated code")
      private String sessionLifetime;
      @SuppressFBWarnings(justification = "generated code")
      private IamSessionSameSitePolicy sameSitePolicy;
      @SuppressFBWarnings(justification = "generated code")
      private AdminappLanguageSettings languageSettings;
      @SuppressFBWarnings(justification = "generated code")
      private StateRepositoryConfig stateRepository;
      @SuppressFBWarnings(justification = "generated code")
      private String customLoginUrl;
      @SuppressFBWarnings(justification = "generated code")
      private String afterLogoutUrl;
      @SuppressFBWarnings(justification = "generated code")
      private Pattern allowedLoginUrlPattern;
      @SuppressFBWarnings(justification = "generated code")
      private Pattern allowedAfterLogoutUrlPattern;
      @SuppressFBWarnings(justification = "generated code")
      private String skin;
      @SuppressFBWarnings(justification = "generated code")
      private boolean skinFromParamAllowed;
      @SuppressFBWarnings(justification = "generated code")
      private String instanceTag;
      @SuppressFBWarnings(justification = "generated code")
      private ContentSecurityPolicyConfig contentSecurityPolicy;
      @SuppressFBWarnings(justification = "generated code")
      private LicenseAnalyticsConfig licenseAnalytics;
      @SuppressFBWarnings(justification = "generated code")
      private UserTrailLogRepositoryConfig logUserTrailToDatabase;
      @SuppressFBWarnings(justification = "generated code")
      private CorrelationIdSettingsConfig correlationIdSettings;
      @SuppressFBWarnings(justification = "generated code")
      private List<ConfigurationExtension> customExtensions;

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

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder startPages(List<String> startPages) {
         this.startPages = startPages;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder users(UsersConfiguration users) {
         this.users = users;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder accessControl(AdminappAccessControllerConfig accessControl) {
         this.accessControl = accessControl;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder maintenanceMessages(MaintenanceMessageConfiguration maintenanceMessages) {
         this.maintenanceMessages = maintenanceMessages;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder administrators(AdministratorsConfiguration administrators) {
         this.administrators = administrators;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder tokens(TokensConfiguration tokens) {
         this.tokens = tokens;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder technicalClients(TechClientsConfig technicalClients) {
         this.technicalClients = technicalClients;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder rest(AdminappRestConfigSpec rest) {
         this.rest = rest;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder eventSettings(AdminappEventSettingsConfig eventSettings) {
         this.eventSettings = eventSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder serviceContainerSharedSecret(String serviceContainerSharedSecret) {
         this.serviceContainerSharedSecret = serviceContainerSharedSecret;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder logViewer(LogViewer logViewer) {
         this.logViewer = logViewer;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder realmAdministration(RealmAdministrationConfig realmAdministration) {
         this.realmAdministration = realmAdministration;
         return this;
      }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder sameSitePolicy(IamSessionSameSitePolicy sameSitePolicy) {
         this.sameSitePolicy = sameSitePolicy;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder languageSettings(AdminappLanguageSettings languageSettings) {
         this.languageSettings = languageSettings;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder customLoginUrl(String customLoginUrl) {
         this.customLoginUrl = customLoginUrl;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder afterLogoutUrl(String afterLogoutUrl) {
         this.afterLogoutUrl = afterLogoutUrl;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder allowedLoginUrlPattern(Pattern allowedLoginUrlPattern) {
         this.allowedLoginUrlPattern = allowedLoginUrlPattern;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder allowedAfterLogoutUrlPattern(Pattern allowedAfterLogoutUrlPattern) {
         this.allowedAfterLogoutUrlPattern = allowedAfterLogoutUrlPattern;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder skin(String skin) {
         this.skin = skin;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder skinFromParamAllowed(boolean skinFromParamAllowed) {
         this.skinFromParamAllowed = skinFromParamAllowed;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder instanceTag(String instanceTag) {
         this.instanceTag = instanceTag;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder contentSecurityPolicy(ContentSecurityPolicyConfig contentSecurityPolicy) {
         this.contentSecurityPolicy = contentSecurityPolicy;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp.AdminappBuilder licenseAnalytics(LicenseAnalyticsConfig licenseAnalytics) {
         this.licenseAnalytics = licenseAnalytics;
         return this;
      }

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public Adminapp build() {
         return new Adminapp(
            this.startPages,
            this.users,
            this.accessControl,
            this.maintenanceMessages,
            this.administrators,
            this.tokens,
            this.technicalClients,
            this.rest,
            this.gatewaySettings,
            this.eventSettings,
            this.serviceContainerSharedSecret,
            this.logViewer,
            this.realmAdministration,
            this.sessionIdleTimeout,
            this.sessionLifetime,
            this.sameSitePolicy,
            this.languageSettings,
            this.stateRepository,
            this.customLoginUrl,
            this.afterLogoutUrl,
            this.allowedLoginUrlPattern,
            this.allowedAfterLogoutUrlPattern,
            this.skin,
            this.skinFromParamAllowed,
            this.instanceTag,
            this.contentSecurityPolicy,
            this.licenseAnalytics,
            this.logUserTrailToDatabase,
            this.correlationIdSettings,
            this.customExtensions
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Adminapp.AdminappBuilder(startPages="
            + this.startPages
            + ", users="
            + this.users
            + ", accessControl="
            + this.accessControl
            + ", maintenanceMessages="
            + this.maintenanceMessages
            + ", administrators="
            + this.administrators
            + ", tokens="
            + this.tokens
            + ", technicalClients="
            + this.technicalClients
            + ", rest="
            + this.rest
            + ", gatewaySettings="
            + this.gatewaySettings
            + ", eventSettings="
            + this.eventSettings
            + ", serviceContainerSharedSecret="
            + this.serviceContainerSharedSecret
            + ", logViewer="
            + this.logViewer
            + ", realmAdministration="
            + this.realmAdministration
            + ", sessionIdleTimeout="
            + this.sessionIdleTimeout
            + ", sessionLifetime="
            + this.sessionLifetime
            + ", sameSitePolicy="
            + this.sameSitePolicy
            + ", languageSettings="
            + this.languageSettings
            + ", stateRepository="
            + this.stateRepository
            + ", customLoginUrl="
            + this.customLoginUrl
            + ", afterLogoutUrl="
            + this.afterLogoutUrl
            + ", allowedLoginUrlPattern="
            + this.allowedLoginUrlPattern
            + ", allowedAfterLogoutUrlPattern="
            + this.allowedAfterLogoutUrlPattern
            + ", skin="
            + this.skin
            + ", skinFromParamAllowed="
            + this.skinFromParamAllowed
            + ", instanceTag="
            + this.instanceTag
            + ", contentSecurityPolicy="
            + this.contentSecurityPolicy
            + ", licenseAnalytics="
            + this.licenseAnalytics
            + ", logUserTrailToDatabase="
            + this.logUserTrailToDatabase
            + ", correlationIdSettings="
            + this.correlationIdSettings
            + ", customExtensions="
            + this.customExtensions
            + ")";
      }
   }
}
