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

import com.airlock.iam.factor.application.configuration.airlock2fa.Airlock2FASettings;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactor;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactorSet;
import com.airlock.iam.flow.api.application.configuration.selection.FlowConditionConfig;
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.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringListProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

@Plugin(
   description = "<p>Condition that is fulfilled if the user has at least one Airlock 2FA device that is active and\ncapable of at least one of the allowed factors.</p>\n<p>The allowed authentication factors are determined as the <strong>common</strong> factors between:\n<ol>\n<li>The factors configured here. These factors are the same for all users.</li>\n<li>The factors that are enabled for the user in the Futurae administration service.</li>\n</ol>\n</p>\n<p>\nIf bypass mode is not enabled in the Airlock 2FA settings, this condition will not be satisfied for users for\nwhich the Futurae bypass mode is enabled. If bypass mode is enabled in the Airlock 2FA settings, this condition\nwill be satisfied for all users for which the Futurae bypass mode is enabled.\n</p>\n",
   displayName = "Has Suitable Airlock 2FA Device"
)
@PluginLicenseRestriction(moduleAffiliation = "Airlock2FA")
public class Airlock2FAHasDeviceConditionConfig implements FlowConditionConfig {
   @StringListProperty(
      description = "<p>List of all enabled factors. The condition is fulfilled, if the user has at least one device that is\ncapable of at least one of the factors listed here.</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.</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.</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.</li>\n     <li><b>Offline QR Code</b>: a QR code is displayed which has to be scanned by a mobile app or hardware token. The device\n         displays a code (OTP) that must be entered manually. This is an offline factor.</li>\n</ul></p>",
      optional = true,
      minElements = 1,
      unique = true,
      defaultValue = {"One-Touch", "Online QR Code", "Passcode", "Offline QR Code"},
      allowedValues = {"One-Touch", "Online QR Code", "Passcode", "Offline QR Code"}
   )
   private List<String> factors;
   @PluginProperty(
      displayName = "Airlock 2FA Settings",
      description = "<p>Settings of Airlock 2FA.</p>\n<p>It is recommended to use the same settings everywhere. Otherwise, behaviour of the condition could seem inconsistent.</p>",
      optional = false,
      globalPluginReference = "globalAirlock2FASettings"
   )
   private Airlock2FASettings airlock2faSettings;
   @BooleanProperty(
      description = "<p>Whether to ignore Cooldown information.</p>\n<p>If disabled, the step ignores the \"Cooldown Period\" for new devices configured in the \"Airlock 2FA Settings\". This is typically used for authentication steps that protect low-risk applications, such as a portal page, which can also be accessed using 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"
   )
   private boolean respectCooldown;

   public Airlock2FAFactorSet getFactors() {
      return Airlock2FAFactorSet.of(this.factors.stream().map(Airlock2FAFactor::ofDisplayName).toList());
   }

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

   public boolean isRespectCooldown() {
      return this.respectCooldown;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public Airlock2FAHasDeviceConditionConfig(List<String> factors, Airlock2FASettings airlock2faSettings, boolean respectCooldown) {
      this.factors = factors;
      this.airlock2faSettings = airlock2faSettings;
      this.respectCooldown = respectCooldown;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class Airlock2FAHasDeviceConditionConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<String> factors;
      @SuppressFBWarnings(justification = "generated code")
      private Airlock2FASettings airlock2faSettings;
      @SuppressFBWarnings(justification = "generated code")
      private boolean respectCooldown;

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

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAHasDeviceConditionConfig.Airlock2FAHasDeviceConditionConfigBuilder factor(String factor) {
         if (this.factors == null) {
            this.factors = new ArrayList<>();
         }

         this.factors.add(factor);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAHasDeviceConditionConfig.Airlock2FAHasDeviceConditionConfigBuilder factors(Collection<? extends String> factors) {
         if (factors == null) {
            throw new NullPointerException("factors cannot be null");
         }

         if (this.factors == null) {
            this.factors = new ArrayList<>();
         }

         this.factors.addAll(factors);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAHasDeviceConditionConfig.Airlock2FAHasDeviceConditionConfigBuilder clearFactors() {
         if (this.factors != null) {
            this.factors.clear();
         }

         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAHasDeviceConditionConfig.Airlock2FAHasDeviceConditionConfigBuilder respectCooldown(boolean respectCooldown) {
         this.respectCooldown = respectCooldown;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAHasDeviceConditionConfig build() {
         return new Airlock2FAHasDeviceConditionConfig(switch (this.factors == null ? 0 : this.factors.size()) {
            case 0 -> Collections.emptyList();
            case 1 -> Collections.singletonList(this.factors.get(0));
            default -> Collections.unmodifiableList(new ArrayList<>(this.factors));
         }, this.airlock2faSettings, this.respectCooldown);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Airlock2FAHasDeviceConditionConfig.Airlock2FAHasDeviceConditionConfigBuilder(factors="
            + this.factors
            + ", airlock2faSettings="
            + this.airlock2faSettings
            + ", respectCooldown="
            + this.respectCooldown
            + ")";
      }
   }
}
