package com.airlock.iam.authentication.application.configuration.migration;

import com.airlock.iam.authentication.application.configuration.migration.condition.NextAuthMethodBasedMigrationConditionConfig;
import com.airlock.iam.flow.api.application.configuration.selection.FlowConditionConfig;
import com.airlock.iam.flow.api.application.configuration.sequence.FlowStepSequence;
import com.airlock.iam.flow.api.application.configuration.step.FlowStepConfig;
import com.airlock.iam.flow.types.api.application.configuration.step.StepConfigForAuthenticationFlows;
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.lifecycle.AfterConfigActivation;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
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.PluginListPropertyValueReader;
import com.airlock.iam.plugin.framework.application.service.builder.plugin.PluginPropertyBuilderInitializer;
import com.airlock.iam.plugin.framework.domain.service.validation.DurationParser;
import com.airlock.iam.plugin.framework.domain.service.validation.DurationValidator;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

@Plugin(
   description = "<p>Simplified configuration of a migration subflow.</p><p>Essentially equivalent to an \"Advanced Migration Selection Option\" with a \"Next Authentication Method-based Migration Condition\" condition and an implicit \"Complete Migration Step\" as the last step. Only the credential activation step needs to be configured.</p>"
)
public final class SimpleMigrationSelectionOptionConfig implements MigrationSelectionOptionConfig {
   @StringProperty(
      description = "Target authentication method of this migration. This is used as follows:<ul><li>As ID in the list of available options (for <tt>POST /&lt;loginapp-uri&gt;/rest/public/authentication/migration/options/retrieve</tt> and <tt>POST /&lt;loginapp-uri&gt;/rest/public/authentication/migration/options/&lt;id&gt;/select</tt>)</li><li>As the \"Target Auth Method\" in the automatically generated \"Next Authentication Method-based Migration Condition\" condition</li><li>As the \"Target Auth Method\" in the automatically generated \"Complete Migration Step\"</li></ul>",
      optional = false,
      validationRegex = "[A-Za-z0-9_-]+",
      suggestedValues = {"AIRLOCK_2FA", "MTAN", "CRONTO", "FIDO", "DEVICE_TOKEN", "MATRIX", "OATH_OTP"}
   )
   private String targetAuthMethod;
   public static final String TARGET_AUTH_METHOD_PROPERTY = "targetAuthMethod";
   @StringProperty(
      description = "<p>Ask the user to migrate during this period before the migration becomes mandatory. Any immediate period (e.g. 0d) leads to the users always being forced to migrate at the migration date and the users not being asked to migrate before the migration date. </p> <p>To always ask the user to migrate as soon as a migration date is set for the user, set this property to a very high value. This setting has no effect if the user has a \"Next Auth Method\" but no migration date set; in this case the user is always asked to migrate and never forced to migrate.</p><p>This property is used to configure the hint period in the automatically created \"Next Authentication Method-based Migration Condition\" condition.</p><p>The duration must be specified in the format \"(d)ays (h)ours (m)inutes (s)econds\" e.g. \"2d 4h 10m 5s\" (any part can be omitted).</p>",
      displayName = "Hint Period",
      optional = true,
      defaultValue = "10d",
      exampleValues = {"7d", "30d", "14d 6h 30m"}
   )
   private String hintPeriod;
   static final String HINT_PERIOD_DISPLAY_NAME = "Hint Period";
   private static final String HINT_PERIOD_PROPERTY = "hintPeriod";
   private Duration hintPeriodDuration;
   private FlowConditionConfig initializedCondition;
   @PluginListProperty(
      description = "Steps of this subflow. A \"Complete Migration Step\" is always automatically configured and added to the end of this step sequence.",
      optional = false
   )
   private List<StepConfigForAuthenticationFlows> steps;
   public static final String STEPS_PROPERTY = "steps";
   private FlowStepSequence flowStepSequence;

   @Override
   public String getName() {
      return this.targetAuthMethod;
   }

   @Override
   public FlowConditionConfig getCondition() {
      return this.initializedCondition;
   }

   private void initializeCondition() {
      this.initializedCondition = ((NextAuthMethodBasedMigrationConditionConfig.NextAuthMethodBasedMigrationConditionConfigBuilder)PluginPropertyBuilderInitializer.builder(
            NextAuthMethodBasedMigrationConditionConfig.NextAuthMethodBasedMigrationConditionConfigBuilder.class
         ))
         .targetAuthMethod(this.targetAuthMethod)
         .hintPeriod(this.hintPeriod)
         .migrationToSameAuthMethodAllowed(true)
         .build();
   }

   @AfterConfigActivation
   void initialize() {
      this.flowStepSequence = this.initializeFlowStepSequence();
      this.parseHintPeriodDuration();
      this.initializeCondition();
   }

   private void parseHintPeriodDuration() {
      this.hintPeriodDuration = new DurationParser(this.hintPeriod).getDuration();
   }

   private FlowStepSequence initializeFlowStepSequence() {
      List<FlowStepConfig> allSteps = new ArrayList<>(this.steps);
      allSteps.add(this.createDefaultCompleteMigrationStep());
      return new FlowStepSequence(allSteps);
   }

   private FlowStepConfig createDefaultCompleteMigrationStep() {
      return ((CompleteMigrationStepConfig.CompleteMigrationStepConfigBuilder)PluginPropertyBuilderInitializer.builder(
            CompleteMigrationStepConfig.CompleteMigrationStepConfigBuilder.class
         ))
         .targetAuthMethod(this.targetAuthMethod)
         .build();
   }

   public SimpleMigrationSelectionOptionConfig(String targetAuthMethod, String hintPeriod, List<StepConfigForAuthenticationFlows> steps) {
      this.targetAuthMethod = targetAuthMethod;
      this.hintPeriod = hintPeriod;
      this.steps = steps;
   }

   @ConfigurationValidator
   static void validateCompleteMigrationNotInSteps(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws ConfigurationException {
      boolean completeMigrationStepConfigured = PluginListPropertyValueReader.effectivePluginListPropertyValue(config, "steps", env, context)
         .stream()
         .filter(Objects::nonNull)
         .<String>map(PluginConfiguration::getPluginClassName)
         .anyMatch(clazz -> CompleteMigrationStepConfig.class.getName().equals(clazz));
      if (completeMigrationStepConfigured) {
         throw new PropertyConstraintViolationException(
            "steps",
            config,
            env,
            context,
            "A \"Complete Migration Step\" is automatically added to the step sequence. If it needs to be configured manually, use an \"Advanced Migration Selection Option\" instead."
         );
      }
   }

   @ConfigurationValidator
   public static void validateHintPeriodDuration(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws ConfigurationException {
      DurationValidator.validateDurationFieldIfSet(config, "hintPeriod", env, context);
   }

   @Override
   public List<StepConfigForAuthenticationFlows> getSteps() {
      return this.steps;
   }

   public Duration getHintPeriodDuration() {
      return this.hintPeriodDuration;
   }

   public String getHintPeriod() {
      return this.hintPeriod;
   }

   @Override
   public FlowStepSequence getFlowStepSequence() {
      return this.flowStepSequence;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class SimpleMigrationSelectionOptionConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String targetAuthMethod;
      @SuppressFBWarnings(justification = "generated code")
      private String hintPeriod;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<StepConfigForAuthenticationFlows> steps;

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

      @SuppressFBWarnings(justification = "generated code")
      public SimpleMigrationSelectionOptionConfig.SimpleMigrationSelectionOptionConfigBuilder targetAuthMethod(String targetAuthMethod) {
         this.targetAuthMethod = targetAuthMethod;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SimpleMigrationSelectionOptionConfig.SimpleMigrationSelectionOptionConfigBuilder hintPeriod(String hintPeriod) {
         this.hintPeriod = hintPeriod;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SimpleMigrationSelectionOptionConfig.SimpleMigrationSelectionOptionConfigBuilder step(StepConfigForAuthenticationFlows step) {
         if (this.steps == null) {
            this.steps = new ArrayList<>();
         }

         this.steps.add(step);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SimpleMigrationSelectionOptionConfig.SimpleMigrationSelectionOptionConfigBuilder steps(
         Collection<? extends StepConfigForAuthenticationFlows> steps
      ) {
         if (steps == null) {
            throw new NullPointerException("steps cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SimpleMigrationSelectionOptionConfig.SimpleMigrationSelectionOptionConfigBuilder clearSteps() {
         if (this.steps != null) {
            this.steps.clear();
         }

         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "SimpleMigrationSelectionOptionConfig.SimpleMigrationSelectionOptionConfigBuilder(targetAuthMethod="
            + this.targetAuthMethod
            + ", hintPeriod="
            + this.hintPeriod
            + ", steps="
            + this.steps
            + ")";
      }
   }
}
