package com.airlock.iam.flow.shared.application.configuration.mtan;

import com.airlock.iam.common.application.configuration.mtan.BasicMtanSettings;
import com.airlock.iam.core.misc.authen.SecretStringGenerator;
import com.airlock.iam.core.misc.impl.authen.ExtendedStringGenerator;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
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.IntegerProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
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.valuereader.BooleanPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Duration;

@Plugin(description = "Settings for mTAN OTP Checks.", displayName = "mTAN OTP Checks Settings")
public final class MtanOtpCheckConfig implements MtanOtpCheckConfigSpec {
   private static final String LAST_TOKEN_AUTOM_SELECTED_DISPLAY_NAME = "Last Token Automatically Selected";
   private static final String AUTO_SELECT_SINGLE_NUMBER_DISPLAY_NAME = "Auto Select Single Number";
   @PluginProperty(description = "Basic mTAN configuration settings.", optional = false, displayName = "Basic mTAN Settings")
   private BasicMtanSettings basicSettings;
   @PluginProperty(
      description = "The string generator plugin to generate the one time password (OTP) token.",
      displayName = "OTP Generator",
      optional = true,
      defaultValue = ExtendedStringGenerator.class
   )
   private SecretStringGenerator otpGenerator;
   @BooleanProperty(
      description = "If enabled, the case of characters is respected when checking OTP tokens.",
      defaultValue = "true",
      optional = true,
      displayName = "Case-Sensitive OTP Check"
   )
   private boolean otpCaseSensitive;
   @IntegerProperty(
      description = "The number of times a user may retry after a wrong OTP is entered before the flow is aborted. If set to zero (the default), only one attempt is possible for each OTP. This is more secure but may increase costs (if sending an SMS is costly) and negatively affects usability.<br/>Setting this value to <code>n</code> means that the user has <code>n+1</code> tries in total. This should not be confused with the limit of failed attempts before the user is locked, which is configured globally for each flow type.",
      minValue = 0,
      defaultValue = 0,
      optional = true,
      exampleValues = {0, 3, 5},
      displayName = "Max Wrong OTP Checks"
   )
   private int maxWrongOtpChecks;
   @IntegerProperty(
      description = "Maximum number of times an OTP token may be requested to be resent during one authentication process. Authentication is aborted if this limit is exceeded. Token retransmissions are disabled if this value is 0. Restricting this value to a small number prevents the abusive use of SMS delivery.",
      optional = true,
      defaultValue = 0,
      minValue = 0,
      exampleValues = {0, 1, 3, 5},
      displayName = "Max OTP Resends"
   )
   private int maxOtpResends;
   @BooleanProperty(
      description = "If token resends are enabled, this property sets whether the OTP token should be resent or whether a new OTP should be generated for each retransmission. Setting this property to <code>true</code> is less secure but helps avoiding erroneous user input when the initial OTP is received before retransmission.",
      optional = true,
      defaultValue = "false",
      displayName = "Resend Same OTP"
   )
   private boolean resendSameOtp;
   @IntegerProperty(
      description = "Determines how long the OTP is valid (in seconds).",
      displayName = "OTP Validity [s]",
      exampleValues = {60, 300, 600},
      minValue = 1,
      defaultValue = 300,
      optional = true
   )
   private int otpValidity;
   @BooleanProperty(
      description = "Controls whether the last used mTAN token is selected by default instead of presenting a selection to the user.",
      displayName = "Last Token Automatically Selected",
      defaultValue = "false",
      optional = true
   )
   private boolean lastTokenAutomaticallySelected;
   static final String LAST_TOKEN_AUTOMATICALLY_SELECTED = "lastTokenAutomaticallySelected";
   @BooleanProperty(
      description = "Controls whether the verification starts automatically or if a single number choice is presented as a selection to the client.",
      displayName = "Auto Select Single Number",
      defaultValue = "true",
      optional = true
   )
   private boolean autoSelectSingleNumber;
   static final String AUTO_SELECT_SINGLE_NUMBER = "autoSelectSingleNumber";

   @Override
   public Duration getOtpValidity() {
      return Duration.ofSeconds(this.otpValidity);
   }

   @Override
   public BasicMtanSettings getBasicSettings() {
      return this.basicSettings;
   }

   @Override
   public SecretStringGenerator getOtpGenerator() {
      return this.otpGenerator;
   }

   @Override
   public boolean isOtpCaseSensitive() {
      return this.otpCaseSensitive;
   }

   @Override
   public int getMaxWrongOtpChecks() {
      return this.maxWrongOtpChecks;
   }

   @Override
   public int getMaxOtpResends() {
      return this.maxOtpResends;
   }

   @Override
   public boolean isResendSameOtp() {
      return this.resendSameOtp;
   }

   @Override
   public boolean isLastTokenAutomaticallySelected() {
      return this.lastTokenAutomaticallySelected;
   }

   @Override
   public boolean isAutoSelectSingleNumber() {
      return this.autoSelectSingleNumber;
   }

   @ConfigurationValidator
   static void checkOptionsForSingleNumbers(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      boolean autoSelectSingleNumber = BooleanPropertyValueReader.effectiveBooleanPropertyValue(config, "autoSelectSingleNumber", env, context);
      boolean lastTokenAutomaticallySelected = BooleanPropertyValueReader.effectiveBooleanPropertyValue(config, "lastTokenAutomaticallySelected", env, context);
      if (!autoSelectSingleNumber && lastTokenAutomaticallySelected) {
         throw new PropertyConstraintViolationException(
            "autoSelectSingleNumber",
            config,
            env,
            context,
            "It is not possible to disable '%s' if '%s' is enabled.".formatted("Auto Select Single Number", "Last Token Automatically Selected")
         );
      }
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public MtanOtpCheckConfig(
      BasicMtanSettings basicSettings,
      SecretStringGenerator otpGenerator,
      boolean otpCaseSensitive,
      int maxWrongOtpChecks,
      int maxOtpResends,
      boolean resendSameOtp,
      int otpValidity,
      boolean lastTokenAutomaticallySelected,
      boolean autoSelectSingleNumber
   ) {
      this.basicSettings = basicSettings;
      this.otpGenerator = otpGenerator;
      this.otpCaseSensitive = otpCaseSensitive;
      this.maxWrongOtpChecks = maxWrongOtpChecks;
      this.maxOtpResends = maxOtpResends;
      this.resendSameOtp = resendSameOtp;
      this.otpValidity = otpValidity;
      this.lastTokenAutomaticallySelected = lastTokenAutomaticallySelected;
      this.autoSelectSingleNumber = autoSelectSingleNumber;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class MtanOtpCheckConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private BasicMtanSettings basicSettings;
      @SuppressFBWarnings(justification = "generated code")
      private SecretStringGenerator otpGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private boolean otpCaseSensitive;
      @SuppressFBWarnings(justification = "generated code")
      private int maxWrongOtpChecks;
      @SuppressFBWarnings(justification = "generated code")
      private int maxOtpResends;
      @SuppressFBWarnings(justification = "generated code")
      private boolean resendSameOtp;
      @SuppressFBWarnings(justification = "generated code")
      private int otpValidity;
      @SuppressFBWarnings(justification = "generated code")
      private boolean lastTokenAutomaticallySelected;
      @SuppressFBWarnings(justification = "generated code")
      private boolean autoSelectSingleNumber;

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

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckConfig.MtanOtpCheckConfigBuilder basicSettings(BasicMtanSettings basicSettings) {
         this.basicSettings = basicSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckConfig.MtanOtpCheckConfigBuilder otpGenerator(SecretStringGenerator otpGenerator) {
         this.otpGenerator = otpGenerator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckConfig.MtanOtpCheckConfigBuilder otpCaseSensitive(boolean otpCaseSensitive) {
         this.otpCaseSensitive = otpCaseSensitive;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckConfig.MtanOtpCheckConfigBuilder maxWrongOtpChecks(int maxWrongOtpChecks) {
         this.maxWrongOtpChecks = maxWrongOtpChecks;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckConfig.MtanOtpCheckConfigBuilder maxOtpResends(int maxOtpResends) {
         this.maxOtpResends = maxOtpResends;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckConfig.MtanOtpCheckConfigBuilder resendSameOtp(boolean resendSameOtp) {
         this.resendSameOtp = resendSameOtp;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckConfig.MtanOtpCheckConfigBuilder otpValidity(int otpValidity) {
         this.otpValidity = otpValidity;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckConfig.MtanOtpCheckConfigBuilder lastTokenAutomaticallySelected(boolean lastTokenAutomaticallySelected) {
         this.lastTokenAutomaticallySelected = lastTokenAutomaticallySelected;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckConfig.MtanOtpCheckConfigBuilder autoSelectSingleNumber(boolean autoSelectSingleNumber) {
         this.autoSelectSingleNumber = autoSelectSingleNumber;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckConfig build() {
         return new MtanOtpCheckConfig(
            this.basicSettings,
            this.otpGenerator,
            this.otpCaseSensitive,
            this.maxWrongOtpChecks,
            this.maxOtpResends,
            this.resendSameOtp,
            this.otpValidity,
            this.lastTokenAutomaticallySelected,
            this.autoSelectSingleNumber
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanOtpCheckConfig.MtanOtpCheckConfigBuilder(basicSettings="
            + this.basicSettings
            + ", otpGenerator="
            + this.otpGenerator
            + ", otpCaseSensitive="
            + this.otpCaseSensitive
            + ", maxWrongOtpChecks="
            + this.maxWrongOtpChecks
            + ", maxOtpResends="
            + this.maxOtpResends
            + ", resendSameOtp="
            + this.resendSameOtp
            + ", otpValidity="
            + this.otpValidity
            + ", lastTokenAutomaticallySelected="
            + this.lastTokenAutomaticallySelected
            + ", autoSelectSingleNumber="
            + this.autoSelectSingleNumber
            + ")";
      }
   }
}
