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

import com.airlock.iam.core.misc.impl.tokenverifier.oathotp.OathOtpSettings;
import com.airlock.iam.core.misc.impl.tokenverifier.oathotp.TotpChallengeHandler;
import com.airlock.iam.flow.api.application.configuration.dynamicactivation.DynamicStepActivationConfig;
import com.airlock.iam.flow.api.application.configuration.selection.FlowConditionConfig;
import com.airlock.iam.flow.api.application.configuration.step.AbstractInteractiveFlowStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.InteractiveGotoTargetConfig;
import com.airlock.iam.flow.api.application.configuration.step.StepIdConfig;
import com.airlock.iam.flow.api.application.configuration.tag.TagConfig;
import com.airlock.iam.flow.api.application.configuration.valueprovider.ValueMapProviderConfig;
import com.airlock.iam.flow.shared.domain.model.oath.OathOtpActivationStepImpl;
import com.airlock.iam.flow.types.api.application.configuration.step.StepConfigForAuthenticationFlows;
import com.airlock.iam.flow.types.api.application.configuration.step.StepConfigForSelfServiceFlows;
import com.airlock.iam.flow.types.api.application.configuration.step.StepConfigForUserSelfRegFlows;
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.NextActions;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.license.PluginLicenseRestriction;
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.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.valuereader.BooleanPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.util.List;
import java.util.Map;

@Plugin(
   displayName = "OATH OTP Activation Step",
   description = "<p>Allows adding or replacing an <a href=https://datatracker.ietf.org/doc/html/rfc6238>OATH (T)OTP</a> token.</p><p>When existing shared secrets are not overwritten, the shared secret is read from the database and presented to the user who may register it in an authenticator app. If the secret has been read into another app, it will remain functional. This can only be done with TOTP tokens, as HOTP token counters will diverge on different devices.</p><p>When used for user self-registration, this step needs an \"OATH Token Insertion Handler\" to be configured.</p><p>This step can trigger two kinds of events, depending on the value of the flag \"Overwrite Existing Shared Secret\":<ul><li>An \"OATH OTP Secret Viewed\" event, if the flag is set to false and the user has an existing secret. This event is triggered whenever this step is entered, without any user interaction (the existing shared secret is communicated to the SPA at the start), and signifies an intended exposure of the secret. Irrespective of how this step ends (success or failure), the (exposed) secret can be used to generate OTPs.</li><li>An \"OATH OTP Secret Added\" event, if a new secret is persisted.</li></ul></p>"
)
@PluginLicenseRestriction(moduleAffiliation = "OathOtp")
@Groups(openGroups = "Basic Settings", groupSequence = {"Basic Settings", "Tags/Guards", "Flow Control"})
@NextActions(OathOtpActivationStepImpl.OathOtpStepActions.class)
public class OathOtpActivationStepConfig
   extends AbstractInteractiveFlowStepConfig
   implements StepConfigForAuthenticationFlows,
   StepConfigForUserSelfRegFlows,
   StepConfigForSelfServiceFlows {
   public static final String OATH_OTP_ACTIVATION_STEP_DISPLAY_NAME = "OATH OTP Activation Step";
   @PluginProperty(
      displayName = "OATH OTP Settings",
      globalPluginReference = "globalOathOtpSettings",
      description = "The OATH OTP Settings.",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private OathOtpSettings oathOtpSettings;
   private static final String OATH_OTP_SETTINGS_DISPLAY_NAME = "OATH OTP Settings";
   static final String OATH_OTP_SETTINGS_PROPERTY_NAME = "oathOtpSettings";
   @BooleanProperty(
      description = "Whether to overwrite (i.e. delete) the old shared secret and set a new one. If this is set to true, existing OATH tokens will not function any longer.\n<p>This setting has no effect if the step is used in a self-registration flow.</p>\n",
      defaultValue = "true",
      displayName = "Overwrite Existing Shared Secret",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean overwriteExistingSharedSecret;
   public static final String OVERWRITE_EXISTING_SHARED_SECRET_DISPLAY_NAME = "Overwrite Existing Shared Secret";
   static final String OVERWRITE_EXISTING_SHARED_SECRET_PROPERTY_NAME = "overwriteExistingSharedSecret";
   @StringProperty(
      displayName = "Authentication Method ID",
      description = "The identifier of the authentication method for this step. Since the authentication method is also the identifier for failed login attempts for this step, distinct identifiers must be chosen if multiple instances of the same step type are used to check different credentials (e.g., two password step instances check two different passwords).<p>Example: a system tracks two passwords per user. Password 1 is used in flow A, password 2 in flow B. These two steps must have distinct 'Authentication Method Ids', e.g. PASSWORD1 and PASSWORD2.</p>If only one identifier (e.g. \"PASSWORD\") is used, this may allow brute force attacks on the password as follows. Assuming an attacker knows the user's password 1, they get an unlimited number of attempts on flow B, as the 'PASSWORD' counter can repeatedly be reset to 0 by performing a successful login on flow A.<p>To prevent such attacks, use two different counters by setting the authentication methods, for example, to PASSWORD1 and PASSWORD2, respectively.</p>",
      optional = true,
      defaultValue = "PASSWORD",
      maxLength = 23
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String authenticationMethodId;

   public OathOtpActivationStepConfig(
      FlowConditionConfig skipCondition,
      FlowConditionConfig preCondition,
      boolean requiresActivation,
      List<TagConfig> tagsOnSuccess,
      @Nullable StepIdConfig stepId,
      List<InteractiveGotoTargetConfig> interactiveGotoTargets,
      List<DynamicStepActivationConfig> dynamicStepActivations,
      Map<String, StepIdConfig> onFailureGotos,
      List<ValueMapProviderConfig> customResponseAttributes,
      List<ValueMapProviderConfig> customFailureResponseAttributes,
      OathOtpSettings oathOtpSettings,
      boolean overwriteExistingSharedSecret,
      String authenticationMethodId
   ) {
      super(
         skipCondition,
         preCondition,
         requiresActivation,
         tagsOnSuccess,
         stepId,
         interactiveGotoTargets,
         dynamicStepActivations,
         onFailureGotos,
         customResponseAttributes,
         customFailureResponseAttributes
      );
      this.oathOtpSettings = oathOtpSettings;
      this.overwriteExistingSharedSecret = overwriteExistingSharedSecret;
      this.authenticationMethodId = authenticationMethodId;
   }

   @ConfigurationValidator
   static void validateOverwriteOnlyWithTotp(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnv) throws ConfigurationException {
      PluginConfiguration otpSettings = PluginPropertyValueReader.effectivePluginPropertyValue(pc, "oathOtpSettings", env, context);
      PluginConfiguration challengeHandler = PluginPropertyValueReader.effectivePluginPropertyValue(otpSettings, "tokenType", env, context);
      boolean overwriteExisting = BooleanPropertyValueReader.effectiveBooleanPropertyValue(pc, "overwriteExistingSharedSecret", env, context);
      if (!overwriteExisting && !TotpChallengeHandler.class.getName().equals(challengeHandler.getPluginClassName())) {
         throw new PropertyConstraintViolationException(
            "oathOtpSettings", pc, env, context, "Only TOTP challenge handler allowed in OATH OTP Settings if not overriding shared secret."
         );
      }
   }

   public OathOtpSettings getOathOtpSettings() {
      return this.oathOtpSettings;
   }

   public boolean isOverwriteExistingSharedSecret() {
      return this.overwriteExistingSharedSecret;
   }

   public String getAuthenticationMethodId() {
      return this.authenticationMethodId;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OathOtpActivationStepConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private FlowConditionConfig skipCondition;
      @SuppressFBWarnings(justification = "generated code")
      private FlowConditionConfig preCondition;
      @SuppressFBWarnings(justification = "generated code")
      private boolean requiresActivation;
      @SuppressFBWarnings(justification = "generated code")
      private List<TagConfig> tagsOnSuccess;
      @SuppressFBWarnings(justification = "generated code")
      private StepIdConfig stepId;
      @SuppressFBWarnings(justification = "generated code")
      private List<InteractiveGotoTargetConfig> interactiveGotoTargets;
      @SuppressFBWarnings(justification = "generated code")
      private List<DynamicStepActivationConfig> dynamicStepActivations;
      @SuppressFBWarnings(justification = "generated code")
      private Map<String, StepIdConfig> onFailureGotos;
      @SuppressFBWarnings(justification = "generated code")
      private List<ValueMapProviderConfig> customResponseAttributes;
      @SuppressFBWarnings(justification = "generated code")
      private List<ValueMapProviderConfig> customFailureResponseAttributes;
      @SuppressFBWarnings(justification = "generated code")
      private OathOtpSettings oathOtpSettings;
      @SuppressFBWarnings(justification = "generated code")
      private boolean overwriteExistingSharedSecret;
      @SuppressFBWarnings(justification = "generated code")
      private String authenticationMethodId;

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

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder skipCondition(FlowConditionConfig skipCondition) {
         this.skipCondition = skipCondition;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder preCondition(FlowConditionConfig preCondition) {
         this.preCondition = preCondition;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder requiresActivation(boolean requiresActivation) {
         this.requiresActivation = requiresActivation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder tagsOnSuccess(List<TagConfig> tagsOnSuccess) {
         this.tagsOnSuccess = tagsOnSuccess;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder stepId(@Nullable StepIdConfig stepId) {
         this.stepId = stepId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder interactiveGotoTargets(List<InteractiveGotoTargetConfig> interactiveGotoTargets) {
         this.interactiveGotoTargets = interactiveGotoTargets;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder dynamicStepActivations(List<DynamicStepActivationConfig> dynamicStepActivations) {
         this.dynamicStepActivations = dynamicStepActivations;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder onFailureGotos(Map<String, StepIdConfig> onFailureGotos) {
         this.onFailureGotos = onFailureGotos;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder customResponseAttributes(List<ValueMapProviderConfig> customResponseAttributes) {
         this.customResponseAttributes = customResponseAttributes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder customFailureResponseAttributes(
         List<ValueMapProviderConfig> customFailureResponseAttributes
      ) {
         this.customFailureResponseAttributes = customFailureResponseAttributes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder oathOtpSettings(OathOtpSettings oathOtpSettings) {
         this.oathOtpSettings = oathOtpSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder overwriteExistingSharedSecret(boolean overwriteExistingSharedSecret) {
         this.overwriteExistingSharedSecret = overwriteExistingSharedSecret;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder authenticationMethodId(String authenticationMethodId) {
         this.authenticationMethodId = authenticationMethodId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OathOtpActivationStepConfig build() {
         return new OathOtpActivationStepConfig(
            this.skipCondition,
            this.preCondition,
            this.requiresActivation,
            this.tagsOnSuccess,
            this.stepId,
            this.interactiveGotoTargets,
            this.dynamicStepActivations,
            this.onFailureGotos,
            this.customResponseAttributes,
            this.customFailureResponseAttributes,
            this.oathOtpSettings,
            this.overwriteExistingSharedSecret,
            this.authenticationMethodId
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OathOtpActivationStepConfig.OathOtpActivationStepConfigBuilder(skipCondition="
            + this.skipCondition
            + ", preCondition="
            + this.preCondition
            + ", requiresActivation="
            + this.requiresActivation
            + ", tagsOnSuccess="
            + this.tagsOnSuccess
            + ", stepId="
            + this.stepId
            + ", interactiveGotoTargets="
            + this.interactiveGotoTargets
            + ", dynamicStepActivations="
            + this.dynamicStepActivations
            + ", onFailureGotos="
            + this.onFailureGotos
            + ", customResponseAttributes="
            + this.customResponseAttributes
            + ", customFailureResponseAttributes="
            + this.customFailureResponseAttributes
            + ", oathOtpSettings="
            + this.oathOtpSettings
            + ", overwriteExistingSharedSecret="
            + this.overwriteExistingSharedSecret
            + ", authenticationMethodId="
            + this.authenticationMethodId
            + ")";
      }
   }
}
