package com.airlock.iam.publicselfservice.application.configuration.steps;

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.sequence.FlowStepSequence;
import com.airlock.iam.flow.api.application.configuration.step.AbstractInteractiveFlowStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.FlowStepConfig;
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.api.domain.model.selection.Option;
import com.airlock.iam.flow.api.domain.model.step.SelectionStep;
import com.airlock.iam.flow.application.configuration.selection.CommonSelectionStepConfig;
import com.airlock.iam.flow.application.configuration.selection.step.InitializedSelectionOptions;
import com.airlock.iam.flow.application.configuration.selection.step.SelectionStepConfigValidator;
import com.airlock.iam.flow.shared.application.configuration.selection.LastSelectionRepositoryConfig;
import com.airlock.iam.flow.shared.application.configuration.selection.SelectionStepLastSelectionValidator;
import com.airlock.iam.flow.types.api.application.configuration.step.StepConfigForPublicSelfServiceFlows;
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.lifecycle.AfterConfigActivation;
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.PluginListProperty;
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 com.airlock.iam.plugin.api.exception.PropertyConfigurationException;
import com.airlock.iam.publicselfservice.application.configuration.selection.SelectionOptionConfigForPublicSelfServiceFlows;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

@Plugin(
   description = "<p>Selection between different subflows depending on configurable options.</p><p>Note that most selection conditions can only work once the user's identity is verified to exist, i.e. after an identity verification step has successfully been completed.</p>",
   displayName = "Selection Step for Public Self-Service"
)
@Groups(openGroups = "Basic Settings", groupSequence = {"Basic Settings", "Tags/Guards", "Flow Control"})
@NextActions(SelectionStep.SelectionStepActions.class)
public final class SelectionStepConfigForPublicSelfServiceFlows
   extends AbstractInteractiveFlowStepConfig
   implements CommonSelectionStepConfig,
   StepConfigForPublicSelfServiceFlows {
   @PluginListProperty(
      description = "All available options for this selection. For each option, a condition can be configured that determines if it is available in a specific situation and for a particular user. If no option is available and a \"Fallback Flow\" is configured, it is executed, otherwise the step fails. If exactly one option is available, it could be selected automatically (see \"Auto Select One Option\" flag). If more than one option is available, the user is asked to interactively select one.",
      optional = false,
      minElements = 1
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<SelectionOptionConfigForPublicSelfServiceFlows> availableOptions;
   @BooleanProperty(
      description = "If only one option is available, it is selected automatically. Disable this setting to always present a choice to the user, even if there is only one option.",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean autoSelectOnlyOption;
   @PluginListProperty(
      description = "Fallback flow that is executed if none of the other options are available. If no fallback flow is configured and no option is fulfilled, the selection fails. To do nothing as a fallback, a flow with a \"No Operation Step\" can be configured.",
      minElements = 1,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<FlowStepConfig> fallbackFlow;
   @PluginProperty(
      description = "A repository which persists the last selection for this step. If this repository is configured, a step ID is mandatory as the selection is identified by the step ID among other things. This also means, that the last selection is lost if the step ID is altered.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private LastSelectionRepositoryConfig lastSelectionRepository;
   private static final String LAST_SELECTION_REPOSITORY_FIELD = "lastSelectionRepository";
   private final List<TagConfig> tagsOnSuccess = List.of();
   private InitializedSelectionOptions initializedOptions;

   @Override
   public List<TagConfig> getTagsOnSuccess() {
      return List.of();
   }

   @ConfigurationValidator
   static void validateOptionNames(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws PropertyConfigurationException {
      SelectionStepConfigValidator.validateOptionNames(pc, env, context);
   }

   SelectionStepConfigForPublicSelfServiceFlows(
      List<SelectionOptionConfigForPublicSelfServiceFlows> availableOptions,
      boolean autoSelectOnlyOption,
      LastSelectionRepositoryConfig lastSelectionRepository,
      List<FlowStepConfig> fallbackFlow,
      FlowConditionConfig skipCondition,
      FlowConditionConfig preCondition,
      boolean requiresActivation,
      @Nullable StepIdConfig stepId,
      List<InteractiveGotoTargetConfig> interactiveGotoTargets,
      List<DynamicStepActivationConfig> dynamicStepActivations,
      Map<String, StepIdConfig> onFailureGotos,
      List<ValueMapProviderConfig> customResponseAttributes,
      List<ValueMapProviderConfig> customFailureResponseAttributes
   ) {
      super(
         skipCondition,
         preCondition,
         requiresActivation,
         List.of(),
         stepId,
         interactiveGotoTargets,
         dynamicStepActivations,
         onFailureGotos,
         customResponseAttributes,
         customFailureResponseAttributes
      );
      this.availableOptions = availableOptions;
      this.autoSelectOnlyOption = autoSelectOnlyOption;
      this.lastSelectionRepository = lastSelectionRepository;
      this.fallbackFlow = fallbackFlow;
   }

   @ConfigurationValidator
   public static void validate(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment) throws ConfigurationException {
      SelectionStepLastSelectionValidator.validateStepIdConfiguredIfLastSelectionUsed(config, env, context);
   }

   @AfterConfigActivation
   void initializeProcessedConfig() {
      this.initializedOptions = InitializedSelectionOptions.createInitializedOptions(this);
   }

   @Override
   public Set<Option> getOptions() {
      return this.initializedOptions.getOptionSet();
   }

   @Override
   public FlowStepSequence getSubFlowForOption(Option option) {
      return this.initializedOptions.getSubFlowForOption(option);
   }

   public Optional<LastSelectionRepositoryConfig> getLastSelectionRepositoryConfig() {
      return Optional.ofNullable(this.lastSelectionRepository);
   }

   @Override
   public List<SelectionOptionConfigForPublicSelfServiceFlows> getAvailableOptions() {
      return this.availableOptions;
   }

   @Override
   public boolean isAutoSelectOnlyOption() {
      return this.autoSelectOnlyOption;
   }

   @Override
   public List<FlowStepConfig> getFallbackFlow() {
      return this.fallbackFlow;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class SelectionStepConfigForPublicSelfServiceFlowsBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<SelectionOptionConfigForPublicSelfServiceFlows> availableOptions;
      @SuppressFBWarnings(justification = "generated code")
      private boolean autoSelectOnlyOption;
      @SuppressFBWarnings(justification = "generated code")
      private LastSelectionRepositoryConfig lastSelectionRepository;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<FlowStepConfig> fallbackFlow;
      @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 StepIdConfig stepId;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<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")
      SelectionStepConfigForPublicSelfServiceFlowsBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder availableOption(
         SelectionOptionConfigForPublicSelfServiceFlows availableOption
      ) {
         if (this.availableOptions == null) {
            this.availableOptions = new ArrayList<>();
         }

         this.availableOptions.add(availableOption);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder availableOptions(
         Collection<? extends SelectionOptionConfigForPublicSelfServiceFlows> availableOptions
      ) {
         if (availableOptions == null) {
            throw new NullPointerException("availableOptions cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder clearAvailableOptions() {
         if (this.availableOptions != null) {
            this.availableOptions.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder autoSelectOnlyOption(boolean autoSelectOnlyOption) {
         this.autoSelectOnlyOption = autoSelectOnlyOption;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder lastSelectionRepository(
         LastSelectionRepositoryConfig lastSelectionRepository
      ) {
         this.lastSelectionRepository = lastSelectionRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder fallbackFlowStep(FlowStepConfig fallbackFlowStep) {
         if (this.fallbackFlow == null) {
            this.fallbackFlow = new ArrayList<>();
         }

         this.fallbackFlow.add(fallbackFlowStep);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder fallbackFlow(
         Collection<? extends FlowStepConfig> fallbackFlow
      ) {
         if (fallbackFlow == null) {
            throw new NullPointerException("fallbackFlow cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder clearFallbackFlow() {
         if (this.fallbackFlow != null) {
            this.fallbackFlow.clear();
         }

         return this;
      }

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder interactiveGotoTarget(
         InteractiveGotoTargetConfig interactiveGotoTarget
      ) {
         if (this.interactiveGotoTargets == null) {
            this.interactiveGotoTargets = new ArrayList<>();
         }

         this.interactiveGotoTargets.add(interactiveGotoTarget);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder interactiveGotoTargets(
         Collection<? extends InteractiveGotoTargetConfig> interactiveGotoTargets
      ) {
         if (interactiveGotoTargets == null) {
            throw new NullPointerException("interactiveGotoTargets cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder clearInteractiveGotoTargets() {
         if (this.interactiveGotoTargets != null) {
            this.interactiveGotoTargets.clear();
         }

         return this;
      }

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SelectionStepConfigForPublicSelfServiceFlows build() {
         List availableOptions = switch (this.availableOptions == null ? 0 : this.availableOptions.size()) {
            case 0 -> Collections.emptyList();
            case 1 -> Collections.singletonList(this.availableOptions.get(0));
            default -> Collections.unmodifiableList(new ArrayList<>(this.availableOptions));
         };

         List fallbackFlow = switch (this.fallbackFlow == null ? 0 : this.fallbackFlow.size()) {
            case 0 -> Collections.emptyList();
            case 1 -> Collections.singletonList(this.fallbackFlow.get(0));
            default -> Collections.unmodifiableList(new ArrayList<>(this.fallbackFlow));
         };

         return new SelectionStepConfigForPublicSelfServiceFlows(
            availableOptions,
            this.autoSelectOnlyOption,
            this.lastSelectionRepository,
            fallbackFlow,
            this.skipCondition,
            this.preCondition,
            this.requiresActivation,
            this.stepId,
            switch (this.interactiveGotoTargets == null ? 0 : this.interactiveGotoTargets.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.interactiveGotoTargets.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.interactiveGotoTargets));
            },
            this.dynamicStepActivations,
            this.onFailureGotos,
            this.customResponseAttributes,
            this.customFailureResponseAttributes
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "SelectionStepConfigForPublicSelfServiceFlows.SelectionStepConfigForPublicSelfServiceFlowsBuilder(availableOptions="
            + this.availableOptions
            + ", autoSelectOnlyOption="
            + this.autoSelectOnlyOption
            + ", lastSelectionRepository="
            + this.lastSelectionRepository
            + ", fallbackFlow="
            + this.fallbackFlow
            + ", skipCondition="
            + this.skipCondition
            + ", preCondition="
            + this.preCondition
            + ", requiresActivation="
            + this.requiresActivation
            + ", stepId="
            + this.stepId
            + ", interactiveGotoTargets="
            + this.interactiveGotoTargets
            + ", dynamicStepActivations="
            + this.dynamicStepActivations
            + ", onFailureGotos="
            + this.onFailureGotos
            + ", customResponseAttributes="
            + this.customResponseAttributes
            + ", customFailureResponseAttributes="
            + this.customFailureResponseAttributes
            + ")";
      }
   }
}
