package com.airlock.iam.login.rest.application.configuration;

import com.airlock.iam.common.api.application.configuration.credential.RequestAuthenticationConfig;
import com.airlock.iam.common.api.application.configuration.credential.RequestAuthenticationConfigForLoginapp;
import com.airlock.iam.common.application.configuration.RestConfigSpec;
import com.airlock.iam.common.application.configuration.secretquestion.SecretQuestionsSettings;
import com.airlock.iam.core.misc.authen.PasswordPolicy;
import com.airlock.iam.core.misc.authen.PasswordService;
import com.airlock.iam.core.misc.authen.PasswordSettings;
import com.airlock.iam.core.misc.authen.UsernameTransformer;
import com.airlock.iam.core.misc.authorization.AccessController;
import com.airlock.iam.flow.shared.application.configuration.devicetoken.DeviceTokenSettings;
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.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 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 = "\t<p>\n\t\tConfigures protected session-less endpoints of the Loginapp REST API. These endpoints require\n\t\tauthentication credentials attached to each request, but don't require previous authentication with a flow.\n\t</p>\n\t<p>\n\t\tThese REST endpoints begin with the resource path <code>/&lt;loginapp-uri&gt;/rest/protected/my/</code>.\n\t</p>\n\t<p>\n\t\tFor most of the session-less protected REST APIs, there is a corresponding flow-based API in the protected self-service REST APIs.\n\t\tWhenever possible, prefer the flow-based variant over the session-less configured here.\n\t</p>\n",
   displayName = "Session-less REST Endpoints",
   typeShortName = "SessionlessRestEndpoints"
)
@Groups(
   groupSequence = {"Basic Settings", "API Access Control", "Security Settings", "Advanced Settings"},
   openGroups = {"Basic Settings", "API Access Control"}
)
public final class LoginappRestConfig implements LoginappRestConfigSpec {
   static final String API_ACCESS_CONTROL = "API Access Control";
   @PluginProperty(
      description = "<p>\n\tConfigures session-less user self-service REST endpoints.\n</p>\n<p>\n\tThese REST endpoints begin with the resource path <code>/&lt;loginapp-uri&gt;/rest/protected/my/</code>.\n</p>\n",
      optional = true,
      displayName = "User Self-Service Settings"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private UserSelfServiceConfig userSelfServiceSettings;
   public static final String USER_SELF_SERVICE_SETTINGS_CONFIG_NAME = "userSelfServiceSettings";
   @PluginProperty(
      description = "<p>\n\tConfigures session-less token REST endpoints related to user tokens.\n</p>\n<p>\n\tThese REST endpoints begin with the resource path <code>/&lt;loginapp-uri&gt;/rest/protected/my/tokens/</code>.\n</p>\n",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private UserTokenConfig userTokenSettings;
   private static final String NON_FLOW_PROTECTED_RESTRICTION = "<p><b>This property is only effective for the protected Loginapp REST API outside of the \"self-service\" sub-path.</b></p>";
   @PluginProperty(
      description = "Determines how a credential is extracted and used to authenticate single requests.<p><b>This property is only effective for the protected Loginapp REST API outside of the \"self-service\" sub-path.</b></p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "API Access Control")
   private RequestAuthenticationConfigForLoginapp requestAuthentication;
   @PluginProperty(
      description = "Controls how authenticated users can access and modify resources. When no access controller is configured, all authenticated incoming requests are per default authorized.<p><b>This property is only effective for the protected Loginapp REST API outside of the \"self-service\" sub-path.</b></p>",
      displayName = "Request Authorization",
      optional = true
   )
   @GroupProperty(groupIdentifier = "API Access Control")
   private AccessController accessController;
   @PluginListProperty(
      displayName = "Username Transformation",
      description = "Transforms user name aliases into real user names for these REST endpoints. This allows for e.g. the email to be used instead of the username.<p>Transformation is done for:<ul><li>Resource URLs: the user id in the REST resource URL (e.g. in self-registration) is transformed before further processing is done.</li><li>Authentication: for protected calls, the username of the provided credential (request credential policy) is transformed before passing it to the configured authenticator.</li></ul></p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "API Access Control")
   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>",
      displayName = "Link Response Rewriting",
      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>In order to produce correct links, the property must be configured up to and including the /rest subpath. IAM will automatically append <code>public</code> to the configured value for calls to the public API and <code>protected</code> for calls to the protected API of the Loginapp (or <code>oauth2</code> for OAuth 2.0/OpenId Connect endpoints).</p><p>Example:<br/><ul><li>Property value: <code>http://myhost:8090/test/rest</code></li><li>The response from the REST call to <code>/&lt;loginapp-uri&gt;/rest/public/users/register</code> will contain a link to <code>http://myhost:8090/test/rest/protected/my/self</code></li></ul></p>",
      exampleValues = "https://myhost:8090/test/rest",
      optional = true,
      displayName = "Base URI",
      validationRegex = ".*(?<!(public/?|protected/?|oauth2/?))$"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String baseUri;

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

   @Override
   public Optional<PasswordService> getPasswordService() {
      return this.getPasswordSettings().map(PasswordSettings::getPasswordService);
   }

   @Override
   public Optional<PasswordSettings> getPasswordSettings() {
      return Optional.ofNullable(this.userSelfServiceSettings).flatMap(UserSelfServiceConfig::getPasswordSettings);
   }

   @Override
   public Optional<PasswordPolicy> getPasswordPolicy() {
      return this.getPasswordSettings().map(PasswordSettings::getPasswordPolicy);
   }

   @Override
   public Optional<RequestAuthenticationConfig> getRequestAuthenticationConfig() {
      return Optional.ofNullable(this.requestAuthentication);
   }

   @Override
   public Optional<AccessController> getAccessController() {
      return Optional.ofNullable(this.accessController);
   }

   @Override
   public Optional<SecretQuestionsSettings> getSecretQuestionsSettings() {
      return Optional.ofNullable(this.userTokenSettings).flatMap(UserTokenConfig::getSecretQuestionsSettings);
   }

   @Override
   public Optional<DeviceTokenSettings> getDeviceTokenSettings() {
      return Optional.ofNullable(this.userTokenSettings).flatMap(UserTokenConfig::getDeviceRegistrationSettings);
   }

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

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

   @Override
   public Optional<UserSelfServiceConfig> getUserSelfServiceSettings() {
      return Optional.ofNullable(this.userSelfServiceSettings);
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public LoginappRestConfig.LoginappRestConfigBuilder toBuilder() {
      LoginappRestConfig.LoginappRestConfigBuilder builder = new LoginappRestConfig.LoginappRestConfigBuilder()
         .userSelfServiceSettings(this.userSelfServiceSettings)
         .userTokenSettings(this.userTokenSettings)
         .requestAuthentication(this.requestAuthentication)
         .accessController(this.accessController)
         .linkResponseRewritingEnabled(this.linkResponseRewritingEnabled)
         .baseUri(this.baseUri);
      if (this.usernameTransformers != null) {
         builder.usernameTransformers(this.usernameTransformers);
      }

      return builder;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public LoginappRestConfig(
      UserSelfServiceConfig userSelfServiceSettings,
      UserTokenConfig userTokenSettings,
      RequestAuthenticationConfigForLoginapp requestAuthentication,
      AccessController accessController,
      List<UsernameTransformer> usernameTransformers,
      boolean linkResponseRewritingEnabled,
      String baseUri
   ) {
      this.userSelfServiceSettings = userSelfServiceSettings;
      this.userTokenSettings = userTokenSettings;
      this.requestAuthentication = requestAuthentication;
      this.accessController = accessController;
      this.usernameTransformers = usernameTransformers;
      this.linkResponseRewritingEnabled = linkResponseRewritingEnabled;
      this.baseUri = baseUri;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class LoginappRestConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private UserSelfServiceConfig userSelfServiceSettings;
      @SuppressFBWarnings(justification = "generated code")
      private UserTokenConfig userTokenSettings;
      @SuppressFBWarnings(justification = "generated code")
      private RequestAuthenticationConfigForLoginapp requestAuthentication;
      @SuppressFBWarnings(justification = "generated code")
      private AccessController accessController;
      @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")
      LoginappRestConfigBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public LoginappRestConfig.LoginappRestConfigBuilder userSelfServiceSettings(UserSelfServiceConfig userSelfServiceSettings) {
         this.userSelfServiceSettings = userSelfServiceSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LoginappRestConfig.LoginappRestConfigBuilder userTokenSettings(UserTokenConfig userTokenSettings) {
         this.userTokenSettings = userTokenSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LoginappRestConfig.LoginappRestConfigBuilder requestAuthentication(RequestAuthenticationConfigForLoginapp requestAuthentication) {
         this.requestAuthentication = requestAuthentication;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LoginappRestConfig.LoginappRestConfigBuilder accessController(AccessController accessController) {
         this.accessController = accessController;
         return this;
      }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public LoginappRestConfig.LoginappRestConfigBuilder 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 LoginappRestConfig.LoginappRestConfigBuilder clearUsernameTransformers() {
         if (this.usernameTransformers != null) {
            this.usernameTransformers.clear();
         }

         return this;
      }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public LoginappRestConfig build() {
         return new LoginappRestConfig(
            this.userSelfServiceSettings,
            this.userTokenSettings,
            this.requestAuthentication,
            this.accessController,
            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
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "LoginappRestConfig.LoginappRestConfigBuilder(userSelfServiceSettings="
            + this.userSelfServiceSettings
            + ", userTokenSettings="
            + this.userTokenSettings
            + ", requestAuthentication="
            + this.requestAuthentication
            + ", accessController="
            + this.accessController
            + ", usernameTransformers="
            + this.usernameTransformers
            + ", linkResponseRewritingEnabled="
            + this.linkResponseRewritingEnabled
            + ", baseUri="
            + this.baseUri
            + ")";
      }
   }
}
