package com.airlock.iam.transactionapproval.application.configuration.flow;

import com.airlock.iam.core.misc.authen.UsernameTransformer;
import com.airlock.iam.flow.api.application.configuration.flowprocessor.FlowProcessorConfig;
import com.airlock.iam.flow.api.application.configuration.flowprocessor.FlowProcessorsConfig;
import com.airlock.iam.flow.api.application.configuration.sequence.FlowStepSequence;
import com.airlock.iam.flow.api.application.configuration.sequence.FlowStepSequence.FlowStepSequenceBuilder;
import com.airlock.iam.flow.api.domain.model.flow.FlowId;
import com.airlock.iam.flow.application.configuration.FlowConfig;
import com.airlock.iam.flow.application.configuration.flow.FlowIdConfig;
import com.airlock.iam.flow.application.configuration.validation.FlowStepIdConfigValidators;
import com.airlock.iam.flow.shared.application.configuration.FlowUserNameTransformerConfig;
import com.airlock.iam.flow.types.api.application.configuration.step.StepConfigForTransactionApprovalFlows;
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.Plugin;
import com.airlock.iam.plugin.api.annotation.license.PluginLicenseRestriction;
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.framework.application.service.builder.plugin.PluginPropertyBuilderInitializer;
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 = "Configuration for a transaction approval flow.", displayName = "Transaction Approval Flow")
@Groups(groupSequence = {"Basic Settings", "Advanced Settings"}, openGroups = "Basic Settings")
@PluginLicenseRestriction(moduleAffiliation = "TransactionApproval")
public final class TransactionApprovalFlowConfig implements FlowConfig, FlowUserNameTransformerConfig {
   public static final String TRANSACTION_APPROVAL_FLOW_DISPLAY_NAME = "Transaction Approval Flow";
   @PluginProperty(description = "Unique ID for this flow, which is used for selecting or referencing a flow.", displayName = "Flow ID", optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private FlowIdConfig flowId;
   public static final String ID_PROPERTY = "flowId";
   @PluginListProperty(description = "Steps of the flow.", optional = false, minElements = 1)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<StepConfigForTransactionApprovalFlows> steps;
   @PluginProperty(
      description = "Processors get notified about the various stages of the flow and offer hooks to plug in custom logic. These processors realize the entire authentication logic such as incrementing failed login counters or checking of user validity. ",
      optional = true,
      defaultValue = DefaultTransactionApprovalProcessorsConfig.class
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private FlowProcessorsConfig processors;
   @PluginListProperty(
      description = "Username transformers may transform the provided username into the single unique user ID required for the flow.<br />The transformation of a username takes place in the first step before the user is loaded. Note that username transformers have no effect on the propagated username value. Transformers can be chained, i.e. a first transformer could normalize the original name, where the next transformer looks up the normalized name in a database for potential transformation matches.<br />In contrary to the above description of chaining, a transformer can also signal that it already found the final user ID and the chain must stop after it.<br/>For further details please refer to the documentation of the username transformer plugins.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<UsernameTransformer> usernameTransformers;

   public FlowId getFlowId() {
      return this.flowId.getId();
   }

   public FlowStepSequence getSteps() {
      return ((FlowStepSequenceBuilder)PluginPropertyBuilderInitializer.pluginBuilder(FlowStepSequenceBuilder.class)).steps(this.steps).build();
   }

   public List<FlowProcessorConfig> getProcessors() {
      return this.processors.getProcessors();
   }

   @ConfigurationValidator
   static void validateFlowConstraints(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      new FlowStepIdConfigValidators(config, env, context, true, new PluginConfiguration[]{config}).validateFlowConstraints();
   }

   public List<UsernameTransformer> getUsernameTransformers() {
      return this.usernameTransformers;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public TransactionApprovalFlowConfig(
      FlowIdConfig flowId, List<StepConfigForTransactionApprovalFlows> steps, FlowProcessorsConfig processors, List<UsernameTransformer> usernameTransformers
   ) {
      this.flowId = flowId;
      this.steps = steps;
      this.processors = processors;
      this.usernameTransformers = usernameTransformers;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class TransactionApprovalFlowConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private FlowIdConfig flowId;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<StepConfigForTransactionApprovalFlows> steps;
      @SuppressFBWarnings(justification = "generated code")
      private FlowProcessorsConfig processors;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<UsernameTransformer> usernameTransformers;

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

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalFlowConfig.TransactionApprovalFlowConfigBuilder flowId(FlowIdConfig flowId) {
         this.flowId = flowId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalFlowConfig.TransactionApprovalFlowConfigBuilder step(StepConfigForTransactionApprovalFlows step) {
         if (this.steps == null) {
            this.steps = new ArrayList<>();
         }

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

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalFlowConfig.TransactionApprovalFlowConfigBuilder steps(Collection<? extends StepConfigForTransactionApprovalFlows> 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 TransactionApprovalFlowConfig.TransactionApprovalFlowConfigBuilder clearSteps() {
         if (this.steps != null) {
            this.steps.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalFlowConfig.TransactionApprovalFlowConfigBuilder processors(FlowProcessorsConfig processors) {
         this.processors = processors;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalFlowConfig.TransactionApprovalFlowConfigBuilder usernameTransformer(UsernameTransformer usernameTransformer) {
         if (this.usernameTransformers == null) {
            this.usernameTransformers = new ArrayList<>();
         }

         this.usernameTransformers.add(usernameTransformer);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalFlowConfig.TransactionApprovalFlowConfigBuilder usernameTransformers(
         Collection<? extends UsernameTransformer> usernameTransformers
      ) {
         if (usernameTransformers == null) {
            throw new NullPointerException("usernameTransformers cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalFlowConfig.TransactionApprovalFlowConfigBuilder clearUsernameTransformers() {
         if (this.usernameTransformers != null) {
            this.usernameTransformers.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalFlowConfig build() {
         List steps = 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));
         };

         return new TransactionApprovalFlowConfig(
            this.flowId, steps, this.processors, switch (this.usernameTransformers == null ? 0 : this.usernameTransformers.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.usernameTransformers.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.usernameTransformers));
            }
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "TransactionApprovalFlowConfig.TransactionApprovalFlowConfigBuilder(flowId="
            + this.flowId
            + ", steps="
            + this.steps
            + ", processors="
            + this.processors
            + ", usernameTransformers="
            + this.usernameTransformers
            + ")";
      }
   }
}
