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

import com.airlock.iam.factor.application.configuration.airlock2fa.Airlock2FASettings;
import com.airlock.iam.factor.application.configuration.airlock2fa.validation.Airlock2FAShortLivedQrCodeConfigValidator;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAShortLivedOnlineQrCodeSettings;
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.airlock2fa.Airlock2FAActivationStep;
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.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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Plugin(
   description = "<p>Step to add a new Airlock 2FA device. This step will generate a QR code (and an Airlock 2FA account if necessary) that needs to be scanned by the device to be added.</p><p>Depending on the use-case, this step should be configured as an 'Authentication Flow Step', 'Protected Self-Service Flow Step' or 'User-Self-Registration Flow Step'.<dl><dt><em>Migration to Airlock 2FA (Authentication Flow)</em></dt><dd>In this case, the user does not yet have any Airlock 2FA device, but already has a different second authentication factor (see Security note) that needs to be migrated to Airlock 2FA. This step needs to be configured as an 'Authentication Flow Step' inside a 'Migration Selection Step'. Upon successful migration, the user will have an Airlock 2FA account and a newly registered Airlock 2FA device that can be used for strong authentication. The user's default authentication method will have been changed to Airlock 2FA.</dd><dt><em>Activation of an Airlock 2FA device (Protected Self-Service Flow)</em></dt><dd>In this case, the user already has a second authentication factor (see Security note) and needs to activate an Airlock 2FA device. This typically happens when the user already has Airlock 2FA as a second authentication factor and needs to activate an additional device. This step needs to be configured as a 'Protected Self-Service Flow Step'. Upon successful activation, the user will have an Airlock 2FA account and a newly registered Airlock 2FA device that can be used for strong authentication. In contrast to the migration scenario above, the user's default authentication method will remain unchanged.</dd><dt><em>Activation of an Airlock 2FA device (User-Self-Registration Flow)</em></dt><dd>In this case, the flow step will register a futurae user account with the device, that was used to scan the activation code. It is required to add an 'Airlock 2FA Token Persisting Handler' in the 'User Persisting Step' to persist the linked futurae user account with the IAM user.</dd></dl></p><p>In the migration and self-service scenarios, an optional 'Airlock 2FA Device Edit Step' can be configured afterwards, to allow the user to edit the newly registered device, e.g., changing its display name.</p><p><b>Note:</b> This step can only register one device in one flow execution. The flow has to be started multiple times when more devices are needed.</p><p><b>Security note:</b> For migration and self-service flows this step should be restricted to <strong>strongly authenticated</strong> users. To do so, a 'Pre-Condition Tag' should be used to ensure that the user is strongly authenticated (using at least one of his pre-existing second authentication factors). In particular, this step should <strong>not</strong> be used for a user authenticated with username and password only. In the password only use-case, the (physical) generation of an 'Airlock 2FA Device Activation Letter' is necessary.</p> ",
   displayName = "Airlock 2FA Activation Step"
)
@PluginLicenseRestriction(moduleAffiliation = "Airlock2FA")
@Groups(openGroups = "Basic Settings", groupSequence = {"Basic Settings", "Short-Lived Online QR Code Settings", "Tags/Guards", "Flow Control"})
@NextActions(Airlock2FAActivationStep.Airlock2FAActivationStepAction.class)
public class Airlock2FAActivationStepConfig
   extends AbstractInteractiveFlowStepConfig
   implements StepConfigForAuthenticationFlows,
   StepConfigForSelfServiceFlows,
   StepConfigForUserSelfRegFlows,
   GenericAirlock2FAActivationStepConfig {
   public static final String AIRLOCK_2FA_ACTIVATION_STEP_DISPLAY_NAME = "Airlock 2FA Activation Step";
   @PluginProperty(
      displayName = "Airlock 2FA Settings",
      description = "Settings of Airlock 2FA.",
      optional = false,
      globalPluginReference = "globalAirlock2FASettings"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private Airlock2FASettings airlock2faSettings;
   @IntegerProperty(
      displayName = "Enrollment Timeout [s]",
      description = "The duration (in seconds) an enrollment QR code should be valid.<p><b>Note:</b> This value is not used when generating activation letters.</p>",
      optional = true,
      exampleValues = 300,
      defaultValue = 300,
      minValue = 60,
      maxValue = 7776000
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private int enrollmentTimeoutSeconds;
   private static final String ENROLLMENT_TIMEOUT_FIELD_NAME = "enrollmentTimeoutSeconds";
   @BooleanProperty(
      defaultValue = "false",
      displayName = "Provide Short Activation Code",
      description = "If enabled, the activation step will provide a short activation code as an additional option to enroll a new device.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean provideActivationCodeShort;
   @BooleanProperty(
      displayName = "Enable Short-Lived Online QR Codes",
      description = "<p>Whether to enable short-lived Online QR Codes. Unlike regular Online QR Codes, these are refreshed regularly, allowing for shorter individual validities.</p>\n<p>Shorter validities enhance security, since forwarding a QR code to victims and tricking them to scan the QR code becomes more difficult if the available time window is small.</p>",
      defaultValue = "false",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Short-Lived Online QR Code Settings")
   private boolean enableShortLivedOnlineQrCodes;
   @IntegerProperty(
      displayName = "QR Code Validity [s]",
      description = "<p>The maximum amount of time in seconds for which an Online QR Code is valid after it is first displayed to\nthe end user (ignoring latency). This duration includes the time defined for the validity overlap.\nIt only limits the time for scanning the QR code, not for the confirmation or approval afterwards.</p>\n<p><strong> Security Notice: The validity duration represents the attack window. Choosing a small validity makes\nattacks more difficult, in cases where an attacker attempts to forward a QR code to a victim for scanning.</strong></p>\n<p>This setting is only active if short-lived Online QR Codes are enabled.</p>",
      optional = true,
      defaultValue = 10,
      minValue = 3
   )
   @GroupProperty(groupIdentifier = "Short-Lived Online QR Code Settings")
   private int shortLivedQrCodeValidity;
   @IntegerProperty(
      displayName = "QR Code Validity Overlap [s]",
      description = "<p>Defines the duration in seconds during which the previously displayed QR Code is still valid after being\nreplaced by the next QR code in sequence.</p>\n<p>This provides time for pending requests to complete and ensures that a valid QR code is displayed at every\nmoment in time, provided that there are no network or performance issues.</p>\n<p>The validity overlap must meet the following criteria:</p>\n<ul>\n<li>It must be smaller than half the overall validity of the QR code.</li>\n<li>It must be larger than the Loginapp UI polling interval (1s) plus the network latency (IAM backend &rarr;\nLoginapp UI plus Mobile Device &rarr; Futurae Backend).<br/>\nNote that the polling interval may differ for custom user interfaces.</li>\n</ul>\n<p>This setting is only active if short-lived Online QR Codes are enabled.</p>",
      optional = true,
      defaultValue = 3,
      minValue = 2
   )
   @GroupProperty(groupIdentifier = "Short-Lived Online QR Code Settings")
   private int shortLivedQrCodeValidityOverlap;

   @ConfigurationValidator(localOnly = true)
   public static void validateActivationCodeShortProperty(
      PluginConfiguration currentConfig, ConfigurationEnvironment env, ConfigurationContext ctx, Environment vEnv
   ) throws ConfigurationException {
      GenericAirlock2FAActivationStepConfig.validateShortLivedSettingsProhibitActivationCodeShort(currentConfig, env, ctx);
   }

   @ConfigurationValidator(localOnly = true)
   public static void validateShortLivedQrCodeSettings(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext ctx, Environment vEnv) throws ConfigurationException {
      Airlock2FAShortLivedQrCodeConfigValidator.runShortLivedSettingsValidation("enrollmentTimeoutSeconds", Optional.empty(), config, env, ctx);
   }

   protected Airlock2FAActivationStepConfig(
      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,
      Airlock2FASettings airlock2faSettings,
      String authenticationMethodId,
      int enrollmentTimeoutSeconds,
      boolean provideActivationCodeShort,
      boolean enableShortLivedOnlineQrCodes,
      int shortLivedQrCodeValidity,
      int shortLivedQrCodeValidityOverlap
   ) {
      super(
         skipCondition,
         preCondition,
         requiresActivation,
         tagsOnSuccess,
         stepId,
         interactiveGotoTargets,
         dynamicStepActivations,
         onFailureGotos,
         customResponseAttributes,
         customFailureResponseAttributes
      );
      this.airlock2faSettings = airlock2faSettings;
      this.enrollmentTimeoutSeconds = enrollmentTimeoutSeconds;
      this.provideActivationCodeShort = provideActivationCodeShort;
      this.enableShortLivedOnlineQrCodes = enableShortLivedOnlineQrCodes;
      this.shortLivedQrCodeValidity = shortLivedQrCodeValidity;
      this.shortLivedQrCodeValidityOverlap = shortLivedQrCodeValidityOverlap;
   }

   public Duration getEnrollmentTimeout() {
      return Duration.ofSeconds(this.enrollmentTimeoutSeconds);
   }

   public Airlock2FASettings getAirlock2faSettings() {
      return this.airlock2faSettings;
   }

   public boolean isProvideActivationCodeShortEnabled() {
      return this.provideActivationCodeShort;
   }

   public Optional<Airlock2FAShortLivedOnlineQrCodeSettings> getShortLivedOnlineQrCodeSettings() {
      return this.enableShortLivedOnlineQrCodes
         ? Optional.of(
            Airlock2FAShortLivedOnlineQrCodeSettings.shortLivedOnlineQrCodeSettings(
               this.shortLivedQrCodeValidity, this.shortLivedQrCodeValidityOverlap, this.enrollmentTimeoutSeconds
            )
         )
         : Optional.empty();
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class Airlock2FAActivationStepConfigBuilder {
      @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 Airlock2FASettings airlock2faSettings;
      @SuppressFBWarnings(justification = "generated code")
      private String authenticationMethodId;
      @SuppressFBWarnings(justification = "generated code")
      private int enrollmentTimeoutSeconds;
      @SuppressFBWarnings(justification = "generated code")
      private boolean provideActivationCodeShort;
      @SuppressFBWarnings(justification = "generated code")
      private boolean enableShortLivedOnlineQrCodes;
      @SuppressFBWarnings(justification = "generated code")
      private int shortLivedQrCodeValidity;
      @SuppressFBWarnings(justification = "generated code")
      private int shortLivedQrCodeValidityOverlap;

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

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

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

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

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

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

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

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAActivationStepConfig.Airlock2FAActivationStepConfigBuilder airlock2faSettings(Airlock2FASettings airlock2faSettings) {
         this.airlock2faSettings = airlock2faSettings;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAActivationStepConfig.Airlock2FAActivationStepConfigBuilder enrollmentTimeoutSeconds(int enrollmentTimeoutSeconds) {
         this.enrollmentTimeoutSeconds = enrollmentTimeoutSeconds;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAActivationStepConfig.Airlock2FAActivationStepConfigBuilder provideActivationCodeShort(boolean provideActivationCodeShort) {
         this.provideActivationCodeShort = provideActivationCodeShort;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAActivationStepConfig.Airlock2FAActivationStepConfigBuilder enableShortLivedOnlineQrCodes(boolean enableShortLivedOnlineQrCodes) {
         this.enableShortLivedOnlineQrCodes = enableShortLivedOnlineQrCodes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAActivationStepConfig.Airlock2FAActivationStepConfigBuilder shortLivedQrCodeValidity(int shortLivedQrCodeValidity) {
         this.shortLivedQrCodeValidity = shortLivedQrCodeValidity;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAActivationStepConfig.Airlock2FAActivationStepConfigBuilder shortLivedQrCodeValidityOverlap(int shortLivedQrCodeValidityOverlap) {
         this.shortLivedQrCodeValidityOverlap = shortLivedQrCodeValidityOverlap;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAActivationStepConfig build() {
         return new Airlock2FAActivationStepConfig(
            this.skipCondition,
            this.preCondition,
            this.requiresActivation,
            this.tagsOnSuccess,
            this.stepId,
            this.interactiveGotoTargets,
            this.dynamicStepActivations,
            this.onFailureGotos,
            this.customResponseAttributes,
            this.customFailureResponseAttributes,
            this.airlock2faSettings,
            this.authenticationMethodId,
            this.enrollmentTimeoutSeconds,
            this.provideActivationCodeShort,
            this.enableShortLivedOnlineQrCodes,
            this.shortLivedQrCodeValidity,
            this.shortLivedQrCodeValidityOverlap
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Airlock2FAActivationStepConfig.Airlock2FAActivationStepConfigBuilder(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
            + ", airlock2faSettings="
            + this.airlock2faSettings
            + ", authenticationMethodId="
            + this.authenticationMethodId
            + ", enrollmentTimeoutSeconds="
            + this.enrollmentTimeoutSeconds
            + ", provideActivationCodeShort="
            + this.provideActivationCodeShort
            + ", enableShortLivedOnlineQrCodes="
            + this.enableShortLivedOnlineQrCodes
            + ", shortLivedQrCodeValidity="
            + this.shortLivedQrCodeValidity
            + ", shortLivedQrCodeValidityOverlap="
            + this.shortLivedQrCodeValidityOverlap
            + ")";
      }
   }
}
