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

import com.airlock.iam.common.api.application.configuration.credential.RequestAuthenticationConfig;
import com.airlock.iam.common.api.application.configuration.credential.RequestAuthenticationConfigForAdminapp;
import com.airlock.iam.common.application.configuration.CorsSettings;
import com.airlock.iam.common.application.configuration.RestConfigSpec;
import com.airlock.iam.common.application.configuration.SmsServiceConfig;
import com.airlock.iam.core.misc.authen.UsernameTransformer;
import com.airlock.iam.core.misc.util.password.hash.PasswordHash;
import com.airlock.iam.core.misc.util.password.hash.SHA256Base64PasswordHash;
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.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.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Plugin(description = "Configures the Adminapp REST interface.", displayName = "Adminapp REST API Configuration")
@Groups(
   groupSequence = {"Basic Settings", "Service Settings", "API Access Control", "Security Settings", "Advanced Settings"},
   openGroups = {"Basic Settings", "Service Settings", "API Access Control"}
)
public final class AdminappRestConfig implements AdminappRestConfigSpec {
   static final String SERVICE_SETTINGS = "Service Settings";
   static final String API_ACCESS_CONTROL = "API Access Control";
   @PluginProperty(
      description = "Enables and configures endpoints for sending SMS and checking the delivery status over the REST API.",
      optional = true,
      displayName = "SMS Service Settings"
   )
   @GroupProperty(groupIdentifier = "Service Settings")
   private SmsServiceConfig smsServiceConfig;
   @PluginProperty(
      description = "Determines how a credential is extracted and used to authenticate single requests.",
      displayName = "Request Authentication",
      optional = false
   )
   @GroupProperty(groupIdentifier = "API Access Control")
   private RequestAuthenticationConfigForAdminapp requestAuthentication;
   public static final String REQUEST_AUTHENTICATION_DISPLAY_NAME = "Request Authentication";
   @PluginProperty(
      description = "The hash function used to hash information that is transferred over the public network.",
      optional = true,
      defaultValue = SHA256Base64PasswordHash.class
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private PasswordHash hashFunction;
   @StringProperty(
      description = "The shared secret to be included in the hashed information. <p>Can be used together with the 'Hash Function' to externalize session information to the client.</p>",
      optional = true,
      isSensitive = true
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private String hashSharedSecret;
   @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;
   @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;
   @PluginListProperty(
      displayName = "Username Transformation",
      description = "Transforms user name aliases in REST resource URLs into real user names.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<UsernameTransformer> usernameTransformers;
   @BooleanProperty(
      description = "Enables rewriting of links in REST responses. If rewriting is disabled or cannot be done correctly due to missing information, the internal URI is written to the response. <p>If a 'Base URI' is configured, links are rewritten according to the configured value. Otherwise, links are rewritten according to the external view provided by the WAF (if configured an a WAF environment cookie is present).</p>",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private boolean linkResponseRewritingEnabled;
   @StringProperty(
      description = "Allows to change the base URI for all links in REST responses.<p>This property is useful in test environments where you want links contained in REST responses to be relative to the configured base URI. Note that configuring this property will take precedence over link rewriting based on the WAF environment cookie.</p><p>Example:<br/>\n<ul>\n<li>Property value: <code>http://myhost:8090/test</code></li>\n<li>The response from the REST call to <code>/&lt;adminapp-uri&gt;/rest/maintenance-messages</code>\nwill contain a link to <code>http://myhost:8090/test/rest/maintenance-messages</code>.</li>\n</ul></p>",
      exampleValues = "https://myhost:8090/test",
      optional = true,
      displayName = "Base URI"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String baseUri;
   @IntegerProperty(
      description = "The amount of records returned by a resource if the page size is not explicitly specified in the request\nby the <code>page[limit]</code> query parameter. Must be greater than 0 and smaller than or equal to the 'Max Page Size'.",
      optional = true,
      minValue = 1,
      defaultValue = 500,
      displayName = "Default Page Size"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private int defaultPageSize;
   private static final String DEFAULT_PAGE_SIZE_PROPERTY = "defaultPageSize";
   @IntegerProperty(
      description = "The maximum amount of records returned by a pageable resource. Must be greater than or equal to the 'Default Page Size'.\nThis parameter also limits the maximum number of displayed results in some searches of the Adminapp.",
      optional = true,
      minValue = 100,
      defaultValue = 5000,
      displayName = "Max Page Size"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private int maxPageSize;

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

   public List<UsernameTransformer> getUsernameTransformers() {
      return List.copyOf(this.usernameTransformers);
   }

   @Override
   public Optional<SmsServiceConfig> getSmsServiceConfig() {
      return Optional.ofNullable(this.smsServiceConfig);
   }

   public boolean isLinkResponseRewritingEnabled() {
      return this.linkResponseRewritingEnabled;
   }

   public Optional<String> getBaseUri() {
      return Optional.ofNullable(this.baseUri);
   }

   @Override
   public Optional<PasswordHash> getHashFunction() {
      return Optional.ofNullable(this.hashFunction);
   }

   @Override
   public Optional<String> getHashSharedSecret() {
      return Optional.ofNullable(this.hashSharedSecret);
   }

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

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

   @Override
   public int getDefaultPageSize() {
      return this.defaultPageSize;
   }

   @Override
   public int getMaxPageSize() {
      return this.maxPageSize;
   }

   @ConfigurationValidator
   static void validateBaseUriAndLinkRewriting(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws ConfigurationException {
      RestConfigSpec.validateBaseUriAndLinkRewriting(config, env, context);
   }

   @ConfigurationValidator
   public static void validateMaxPageSize(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      int maxPageSize = getConfiguredMaxPageSize(config, env, context);
      int defaultPageSize = getConfiguredDefaultPageSize(config, env, context);
      if (maxPageSize < defaultPageSize) {
         throw new PropertyConstraintViolationException(
            "maxPageSize", config, env, context, "The Max Page Size must be greater than or equal to the Default Page Size"
         );
      }
   }

   private static int getConfiguredMaxPageSize(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyTypeMismatchException {
      String maxPageSizeString = config.getSimplePropertyConfigurationValue("maxPageSize", env, context);
      return maxPageSizeString != null ? Integer.parseInt(maxPageSizeString) : 5000;
   }

   private static int getConfiguredDefaultPageSize(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyTypeMismatchException {
      String defaultPageSizeString = config.getSimplePropertyConfigurationValue("defaultPageSize", env, context);
      return defaultPageSizeString != null ? Integer.parseInt(defaultPageSizeString) : 500;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public AdminappRestConfig(
      SmsServiceConfig smsServiceConfig,
      RequestAuthenticationConfigForAdminapp requestAuthentication,
      PasswordHash hashFunction,
      String hashSharedSecret,
      CorsSettings corsSettings,
      boolean csrfProtection,
      List<UsernameTransformer> usernameTransformers,
      boolean linkResponseRewritingEnabled,
      String baseUri,
      int defaultPageSize,
      int maxPageSize
   ) {
      this.smsServiceConfig = smsServiceConfig;
      this.requestAuthentication = requestAuthentication;
      this.hashFunction = hashFunction;
      this.hashSharedSecret = hashSharedSecret;
      this.corsSettings = corsSettings;
      this.csrfProtection = csrfProtection;
      this.usernameTransformers = usernameTransformers;
      this.linkResponseRewritingEnabled = linkResponseRewritingEnabled;
      this.baseUri = baseUri;
      this.defaultPageSize = defaultPageSize;
      this.maxPageSize = maxPageSize;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class AdminappRestConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private SmsServiceConfig smsServiceConfig;
      @SuppressFBWarnings(justification = "generated code")
      private RequestAuthenticationConfigForAdminapp requestAuthentication;
      @SuppressFBWarnings(justification = "generated code")
      private PasswordHash hashFunction;
      @SuppressFBWarnings(justification = "generated code")
      private String hashSharedSecret;
      @SuppressFBWarnings(justification = "generated code")
      private CorsSettings corsSettings;
      @SuppressFBWarnings(justification = "generated code")
      private boolean csrfProtection;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<UsernameTransformer> usernameTransformers;
      @SuppressFBWarnings(justification = "generated code")
      private boolean linkResponseRewritingEnabled;
      @SuppressFBWarnings(justification = "generated code")
      private String baseUri;
      @SuppressFBWarnings(justification = "generated code")
      private int defaultPageSize;
      @SuppressFBWarnings(justification = "generated code")
      private int maxPageSize;

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

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder smsServiceConfig(SmsServiceConfig smsServiceConfig) {
         this.smsServiceConfig = smsServiceConfig;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder requestAuthentication(RequestAuthenticationConfigForAdminapp requestAuthentication) {
         this.requestAuthentication = requestAuthentication;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder hashFunction(PasswordHash hashFunction) {
         this.hashFunction = hashFunction;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder hashSharedSecret(String hashSharedSecret) {
         this.hashSharedSecret = hashSharedSecret;
         return this;
      }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder usernameTransformer(UsernameTransformer usernameTransformer) {
         if (this.usernameTransformers == null) {
            this.usernameTransformers = new ArrayList<>();
         }

         this.usernameTransformers.add(usernameTransformer);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder usernameTransformers(Collection<? extends UsernameTransformer> usernameTransformers) {
         if (usernameTransformers == null) {
            throw new NullPointerException("usernameTransformers cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder clearUsernameTransformers() {
         if (this.usernameTransformers != null) {
            this.usernameTransformers.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder linkResponseRewritingEnabled(boolean linkResponseRewritingEnabled) {
         this.linkResponseRewritingEnabled = linkResponseRewritingEnabled;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder baseUri(String baseUri) {
         this.baseUri = baseUri;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder defaultPageSize(int defaultPageSize) {
         this.defaultPageSize = defaultPageSize;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig.AdminappRestConfigBuilder maxPageSize(int maxPageSize) {
         this.maxPageSize = maxPageSize;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminappRestConfig build() {
         return new AdminappRestConfig(
            this.smsServiceConfig,
            this.requestAuthentication,
            this.hashFunction,
            this.hashSharedSecret,
            this.corsSettings,
            this.csrfProtection,
            switch (this.usernameTransformers == null ? 0 : this.usernameTransformers.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.usernameTransformers.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.usernameTransformers));
            },
            this.linkResponseRewritingEnabled,
            this.baseUri,
            this.defaultPageSize,
            this.maxPageSize
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "AdminappRestConfig.AdminappRestConfigBuilder(smsServiceConfig="
            + this.smsServiceConfig
            + ", requestAuthentication="
            + this.requestAuthentication
            + ", hashFunction="
            + this.hashFunction
            + ", hashSharedSecret="
            + this.hashSharedSecret
            + ", corsSettings="
            + this.corsSettings
            + ", csrfProtection="
            + this.csrfProtection
            + ", usernameTransformers="
            + this.usernameTransformers
            + ", linkResponseRewritingEnabled="
            + this.linkResponseRewritingEnabled
            + ", baseUri="
            + this.baseUri
            + ", defaultPageSize="
            + this.defaultPageSize
            + ", maxPageSize="
            + this.maxPageSize
            + ")";
      }
   }
}
