package com.airlock.iam.common.application.configuration.rememberme;

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.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.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.framework.domain.service.validation.DurationParser;
import com.airlock.iam.plugin.framework.domain.service.validation.DurationValidator;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.time.Duration;
import java.util.Optional;

@Plugin(
   description = "<p>Settings for the Remember-Me feature that allows to skip certain authentication steps if a valid token is present in the request.</p><p>When using this feature, the Remember-Me cookie must be configured as an encrypted pass-through cookie on the Airlock Gateway.</p>",
   displayName = "Remember-Me Settings"
)
@Groups(groupSequence = {"Basic Settings", "Cookie Settings"}, openGroups = "Basic Settings")
public final class RememberMeConfig {
   public static final String ENTRYPATH = "%ENTRYPATH%";
   static final String COOKIE_SETTINGS = "Cookie Settings";
   @PluginProperty(description = "Configures the repository to store Remember-Me data.", optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private RememberMeRepositoryConfig repository;
   @EnumProperty(
      description = "This property controls what happens when a user explicitly logs out. It determines whether an explicit logout removes or keeps the Remember-Me cookie.",
      optional = true,
      defaultValue = "REMOVE_COOKIE"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private RememberMeConfig.LogoutBehaviour logoutBehavior;
   @BooleanProperty(
      description = "If enabled, Remember-Me tokens will be removed on password changes.",
      displayName = "Remove Remember-Me Tokens On Password Changes",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean removeRememberMeTokensOnPasswordChanges;
   @BooleanProperty(
      description = "If enabled, Remember-Me tokens will be removed on locked users.",
      displayName = "Remove Remember-Me Tokens On User Locked",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean removeRememberMeTokensOnUserLocked;
   @StringProperty(
      description = "The maximum duration the user will be remembered for. After this time, the token will be invalidated no matter whether it has been used recently or not.<p>Duration must be specified like \"2d 4h 10m 5s\" or any part thereof.</p>",
      optional = true,
      defaultValue = "7d",
      exampleValues = {"10d", "8h", "2d 12h"}
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String lifetime;
   public static final String LIFETIME_FIELD = "lifetime";
   @StringProperty(
      description = "The optional idle timeout of the Remember-Me token. If a token hasn't been used for this amount of time, it will be invalidated no matter whether it has reached its lifetime or not. When this property is not set, a token will only expire when its lifetime has been reached or it has been invalidated by other means.<p>Duration must be specified like \"2d 4h 10m 5s\" or any part thereof.</p>",
      optional = true,
      exampleValues = {"10d", "8h", "2d 12h"}
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String idleTimeout;
   private static final String IDLE_TIMEOUT_FIELD = "idleTimeout";
   @StringProperty(
      description = "The name of the Remember-Me cookie.<p>If this name is changed, the Airlock Gateway has to be reconfigured to pass-through and encrypt this cookie.</p>",
      exampleValues = "RememberMe",
      optional = true,
      defaultValue = "RememberMe"
   )
   @GroupProperty(groupIdentifier = "Cookie Settings")
   private String cookieName;
   @StringProperty(
      description = "The domain for which the cookie is set. If left empty, it will be automatically be sent back only to the originating domain which set the cookie in the first place.",
      exampleValues = {"www.airlock.com", "airlock.com"},
      validationRegex = "(?:^[^.].*$|^$)",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Cookie Settings")
   private String cookieDomain;
   @StringProperty(
      description = "The path for which the cookie is set. The path determines where the cookie is sent to by the browser.<p>Use the variable \"%ENTRYPATH%\" to automatically set the correct path even if used behind an Airlock Gateway.</p>",
      exampleValues = {"%ENTRYPATH%", "/", "/auth"},
      optional = true,
      defaultValue = "%ENTRYPATH%"
   )
   @GroupProperty(groupIdentifier = "Cookie Settings")
   private String cookiePath;
   private Duration rememberMeLifetimeDuration;
   private Optional<Duration> rememberMeIdleTimeoutDuration;

   public RememberMeConfig(
      RememberMeRepositoryConfig repository,
      RememberMeConfig.LogoutBehaviour logoutBehavior,
      boolean removeRememberMeTokensOnPasswordChanges,
      boolean removeRememberMeTokensOnUserLocked,
      String lifetime,
      String idleTimeout,
      String cookieName,
      String cookieDomain,
      String cookiePath
   ) {
      this.repository = repository;
      this.logoutBehavior = logoutBehavior;
      this.removeRememberMeTokensOnPasswordChanges = removeRememberMeTokensOnPasswordChanges;
      this.removeRememberMeTokensOnUserLocked = removeRememberMeTokensOnUserLocked;
      this.lifetime = lifetime;
      this.idleTimeout = idleTimeout;
      this.cookieName = cookieName;
      this.cookieDomain = cookieDomain;
      this.cookiePath = cookiePath;
   }

   public String getCookiePath(String currentFrontendPath) {
      return "%ENTRYPATH%".equals(this.cookiePath) ? currentFrontendPath : this.cookiePath;
   }

   @ConfigurationValidator
   public static void checkRememberMeTimeouts(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      DurationValidator.validateDurationFieldIfSet(config, "lifetime", env, context);
      DurationValidator.validateDurationFieldIfSet(config, "idleTimeout", env, context);
   }

   @AfterConfigActivation
   void parseLifetimes() {
      this.rememberMeLifetimeDuration = new DurationParser(this.lifetime).getDuration();

      try {
         this.rememberMeIdleTimeoutDuration = Optional.of(new DurationParser(this.idleTimeout).getDuration());
      } catch (IllegalArgumentException e) {
         this.rememberMeIdleTimeoutDuration = Optional.empty();
      }
   }

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

   public RememberMeConfig.LogoutBehaviour getLogoutBehavior() {
      return this.logoutBehavior;
   }

   public boolean isRemoveRememberMeTokensOnPasswordChanges() {
      return this.removeRememberMeTokensOnPasswordChanges;
   }

   public boolean isRemoveRememberMeTokensOnUserLocked() {
      return this.removeRememberMeTokensOnUserLocked;
   }

   public String getCookieName() {
      return this.cookieName;
   }

   @Nullable
   public String getCookieDomain() {
      return this.cookieDomain;
   }

   public Duration getRememberMeLifetimeDuration() {
      return this.rememberMeLifetimeDuration;
   }

   public Optional<Duration> getRememberMeIdleTimeoutDuration() {
      return this.rememberMeIdleTimeoutDuration;
   }

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

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

   public enum LogoutBehaviour {
      REMOVE_COOKIE,
      KEEP_COOKIE;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class RememberMeConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private RememberMeRepositoryConfig repository;
      @SuppressFBWarnings(justification = "generated code")
      private RememberMeConfig.LogoutBehaviour logoutBehavior;
      @SuppressFBWarnings(justification = "generated code")
      private boolean removeRememberMeTokensOnPasswordChanges;
      @SuppressFBWarnings(justification = "generated code")
      private boolean removeRememberMeTokensOnUserLocked;
      @SuppressFBWarnings(justification = "generated code")
      private String lifetime;
      @SuppressFBWarnings(justification = "generated code")
      private String idleTimeout;
      @SuppressFBWarnings(justification = "generated code")
      private String cookieName;
      @SuppressFBWarnings(justification = "generated code")
      private String cookieDomain;
      @SuppressFBWarnings(justification = "generated code")
      private String cookiePath;

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

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeConfig.RememberMeConfigBuilder repository(RememberMeRepositoryConfig repository) {
         this.repository = repository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeConfig.RememberMeConfigBuilder logoutBehavior(RememberMeConfig.LogoutBehaviour logoutBehavior) {
         this.logoutBehavior = logoutBehavior;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeConfig.RememberMeConfigBuilder removeRememberMeTokensOnPasswordChanges(boolean removeRememberMeTokensOnPasswordChanges) {
         this.removeRememberMeTokensOnPasswordChanges = removeRememberMeTokensOnPasswordChanges;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeConfig.RememberMeConfigBuilder removeRememberMeTokensOnUserLocked(boolean removeRememberMeTokensOnUserLocked) {
         this.removeRememberMeTokensOnUserLocked = removeRememberMeTokensOnUserLocked;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeConfig.RememberMeConfigBuilder lifetime(String lifetime) {
         this.lifetime = lifetime;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeConfig.RememberMeConfigBuilder idleTimeout(String idleTimeout) {
         this.idleTimeout = idleTimeout;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeConfig.RememberMeConfigBuilder cookieName(String cookieName) {
         this.cookieName = cookieName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeConfig.RememberMeConfigBuilder cookieDomain(String cookieDomain) {
         this.cookieDomain = cookieDomain;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeConfig.RememberMeConfigBuilder cookiePath(String cookiePath) {
         this.cookiePath = cookiePath;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeConfig build() {
         return new RememberMeConfig(
            this.repository,
            this.logoutBehavior,
            this.removeRememberMeTokensOnPasswordChanges,
            this.removeRememberMeTokensOnUserLocked,
            this.lifetime,
            this.idleTimeout,
            this.cookieName,
            this.cookieDomain,
            this.cookiePath
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "RememberMeConfig.RememberMeConfigBuilder(repository="
            + this.repository
            + ", logoutBehavior="
            + this.logoutBehavior
            + ", removeRememberMeTokensOnPasswordChanges="
            + this.removeRememberMeTokensOnPasswordChanges
            + ", removeRememberMeTokensOnUserLocked="
            + this.removeRememberMeTokensOnUserLocked
            + ", lifetime="
            + this.lifetime
            + ", idleTimeout="
            + this.idleTimeout
            + ", cookieName="
            + this.cookieName
            + ", cookieDomain="
            + this.cookieDomain
            + ", cookiePath="
            + this.cookiePath
            + ")";
      }
   }
}
