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

import com.airlock.iam.factor.application.configuration.airlock2fa.Airlock2FARepositoryConfig;
import com.airlock.iam.factor.application.configuration.airlock2fa.Airlock2FASettings;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FACooldownSettings;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactor;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactorPriority;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAMobileOnlyRedirectUri;
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.plugin.api.annotation.config.ConfigurationValidator;
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.StringListProperty;
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 jakarta.annotation.Nullable;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public abstract class Airlock2FAApprovalStepConfig extends AbstractInteractiveFlowStepConfig {
   protected static final String PUSH_TO_ALL_DESCRIPTION = "If Push-to-All for One-Touch is enabled, device selection is never required for One-Touch. Push notifications are sent to all devices of a user and approval can be given on any of the devices.<p>The combination of Push-to-All and \"Lock User on Fraud\" could have undesired effects, because users might report fraud in legitimate use-cases.</p>";
   public static final String MOBILE_ONLY_SETTINGS = "Mobile Only Settings";
   @PluginProperty(
      displayName = "Airlock 2FA Settings",
      description = "Settings of Airlock 2FA.",
      optional = false,
      globalPluginReference = "globalAirlock2FASettings"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private Airlock2FASettings airlock2faSettings;
   @StringProperty(
      description = "The URI used on iOS to switch from the authentication app (Airlock 2FA or compatible) back to the main app where the authentication was initiated. This value is not applicable if the authentication is directly performed by the main app. Furthermore, this value is ignored on Android, where the back stack mechanism is used instead.",
      displayName = "Redirect URI",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Mobile Only Settings")
   private String mobileOnlyRedirectUri;
   @StringProperty(
      description = "Allows to override the scheme of the mobile authentication URI which is returned by the approval step.\nThe scheme of the authentication URI determines, which app to open on the mobile device for authentication.\nThis is useful when migrating from one authentication app (such as Airlock 2FA) to a different authentication app.\nFor example, during a migration, this step can be used to support the previous authentication app by overriding\nthe scheme of the new app with the scheme of the previous app.\n",
      displayName = "Scheme Override",
      validationRegex = "^[a-zA-Z][a-zA-Z0-9\\+\\-\\.]*$",
      exampleValues = {"airlock2fa", "OneApp-1"},
      optional = true
   )
   @GroupProperty(groupIdentifier = "Mobile Only Settings")
   private String mobileOnlySchemeOverride;
   @StringListProperty(
      description = "<p>Priority list of all factors that can be used in this approval step. Only factors that are in this list can be used.\nThe factors are offered in the configured order.</p>\n<p>One-Touch and Online QR Code must come before all other factors.\nIt is recommended to include at least one offline factor.</p>\n<p>Available factors:\n  <ul>\n     <li><b>One-Touch</b>: a push message is sent to the user's mobile app, where it must be approved. This is an online factor and will\n         require device selection if the user has multiple devices.</li>\n     <li><b>Online QR Code</b>: a QR code is displayed in the browser, which has to be scanned by a mobile app and approved there. This\n         is an online factor. No prior device selection is required.</li>\n     <li><b>Offline QR Code</b>: a QR code is displayed in the browser which has to be scanned by a mobile app or hardware token. The device\n         displays a code (OTP) that must be entered manually in the browser. This is an offline factor and will\n         require device selection if the user has multiple devices.</li>\n     <li><b>Passcode</b>: the device (mobile app or hardware token) generates a time-dependent code (OTP) that has to be entered manually.\n     \tThis is an offline factor. No prior device selection is required.</li>\n     <li><b>Mobile Only</b>: the approval is handled directly by the mobile app. This is an online factor. No prior device selection is required.\n     There is no fallback from this factor to other factors or vice-versa. Therefore, the only use case for combining this with other factors is\n     in transaction approval, where the factor previously used for authentication determines whether mobile-only or another factor\n     will be used. Since there is no way to use any factors configured after Mobile Only, it should always be configured as the last factor.</li>\n</ul></p>\n<p><b>AuthTokenId</b>:<br/>\n    The AuthTokenId identifies the device and factor that was used during the authentication\n    and links it to the approval process. It is used to ensure that, for certain flows, the same device\n    must perform the approval. The AuthTokenId is evaluated <b>only for transaction approval</b>. It has no\n    effect on other flow types.</p> <p>When the AuthTokenId is present in the transaction approval flow and contains the\n    factor <i>Mobile Only</i>:\n<ul>\n    <li>and the <i>Mobile Only</i> factor is configured in this approval step: the Mobile Only factor will be enforced.</li>\n    <li>and the <i>Mobile Only</i> factor is not configured in this approval step: any of the configured factors may be used.</li>\n</ul></p>",
      optional = true,
      minElements = 1,
      unique = true,
      defaultValue = {"One-Touch", "Offline QR Code", "Mobile Only"},
      allowedValues = {"One-Touch", "Online QR Code", "Offline QR Code", "Mobile Only", "Passcode"}
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<String> approvalFactors;
   public static final String APPROVAL_FACTORS_PROPERTY = "approvalFactors";
   @BooleanProperty(
      description = "<p>If enabled, devices in cooldown cannot be used for approval.</p><p>If disabled, this step ignores the \"Cooldown Period\" for new devices configured in the \"Airlock 2FA Settings\". This is typically used for approval steps that protect low-risk operations, which can also be performed with devices in cooldown.</p><p>If no \"Cooldown Period\" is defined, enabling this property has no effect.</p>",
      displayName = "Respect Cooldown Period",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private boolean respectCooldownPeriod;

   protected Airlock2FAApprovalStepConfig(
      FlowConditionConfig skipCondition,
      FlowConditionConfig preCondition,
      List<String> approvalFactors,
      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,
      @Nullable String mobileOnlyRedirectUri,
      boolean respectCooldownPeriod
   ) {
      super(
         skipCondition,
         preCondition,
         requiresActivation,
         tagsOnSuccess,
         stepId,
         interactiveGotoTargets,
         dynamicStepActivations,
         onFailureGotos,
         customResponseAttributes,
         customFailureResponseAttributes
      );
      this.airlock2faSettings = airlock2faSettings;
      this.mobileOnlyRedirectUri = mobileOnlyRedirectUri;
      this.approvalFactors = approvalFactors;
      this.respectCooldownPeriod = respectCooldownPeriod;
   }

   public Airlock2FARepositoryConfig getRepository() {
      return this.airlock2faSettings.getRepository();
   }

   public Optional<Airlock2FAMobileOnlyRedirectUri> getMobileOnlyRedirectUri() {
      return Optional.ofNullable(this.mobileOnlyRedirectUri).map(Airlock2FAMobileOnlyRedirectUri::new);
   }

   public Optional<String> getMobileOnlySchemeOverride() {
      return Optional.ofNullable(this.mobileOnlySchemeOverride);
   }

   public Airlock2FAFactorPriority getApprovalFactors() {
      return new Airlock2FAFactorPriority(this.approvalFactors.stream().map(Airlock2FAFactor::ofDisplayName).toList());
   }

   public Airlock2FACooldownSettings getCooldownSettings() {
      return new Airlock2FACooldownSettings(this.airlock2faSettings.getCooldownDuration(), this.respectCooldownPeriod);
   }

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

   @ConfigurationValidator
   public static void validateFactors(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment valenv) throws ConfigurationException {
      Airlock2FASettings.validateFactors(config, "approvalFactors", env, context);
   }

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