package com.airlock.iam.flow.api.application.configuration.step;

import com.airlock.iam.flow.api.application.configuration.selection.FlowConditionConfig;
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.step.StepId;
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.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginMapProperty;
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.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.BooleanPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
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 AbstractFlowStepConfig implements FlowStepConfig {
   public static final String TAGS_GROUP = "Tags/Guards";
   public static final String GOTO_GROUP = "Flow Control";
   public static final String ADVANCED_SETTINGS = "Advanced Settings";
   @PluginProperty(
      description = "<p>If this condition is configured and fulfilled, the step is skipped and the flow execution continues with the subsequent step.</p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Tags/Guards")
   private FlowConditionConfig skipCondition;
   @PluginProperty(
      description = "This step is executed only if the configured pre condition is fulfilled. If the condition is not fulfilled, the step and flow execution fail immediately. The step is not initialized and no step method can be called. If no condition is configured, the behavior is that of a fulfilled pre condition.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Tags/Guards")
   private FlowConditionConfig preCondition;
   @BooleanProperty(
      description = "If enabled, this step is only executed if it has been dynamically activated from a previous step. If it has not been activated, the step is skipped (equivalent to when the skip condition is fulfilled).",
      optional = true,
      defaultValue = "false"
   )
   @GroupProperty(groupIdentifier = "Flow Control")
   private boolean requiresActivation;
   public static final String REQUIRES_ACTIVATION_FIELD = "requiresActivation";
   @PluginListProperty(description = "This step grants these tags if it completes successfully.", optional = true)
   @GroupProperty(groupIdentifier = "Tags/Guards")
   private List<TagConfig> tagsOnSuccess;
   @PluginProperty(
      description = "ID of this step. This is only needed if this step is the <em>target</em> of a goto action or if this step requires activation.",
      optional = true,
      displayName = "Step ID"
   )
   @GroupProperty(groupIdentifier = "Flow Control")
   private StepIdConfig stepId;
   public static final String STEP_ID_FIELD = "stepId";
   public static final String STEP_ID_DISPLAY_NAME = "Step ID";
   @PluginMapProperty(
      description = "<p>If the step fails (no retry) and a goto target for the error code is defined here, the flow does not fail\nand instead a \"goto\" to the specified target step is executed. Note that even when the \"goto\" is executed, any\nerror codes that are considered a failed factor attempt will still increment the \"failed attempts\" counter,\nand may lead to the user being locked. Therefore, this may still result in a failed flow.</p>\n<p>A typical application of this feature is switching to an alternative authentication factor step, if an external\nservice (e.g. Futurae server, SMS gateway) is not available (error code <code>EXTERNAL_SERVICE_UNAVAILABLE</code>\nwith \"Strict Counting\" disabled, which will not increment the \"failed attempts\" counter).\nOther error codes can be found in the IAM REST documentation, in both the general \"Error Codes\" section and\nin the documentation of specific endpoints.</p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Flow Control")
   private Map<String, StepIdConfig> onFailureGotos;
   public static final String ON_FAILURE_GOTOS_FIELD = "onFailureGotos";
   @PluginListProperty(
      description = "<p>A list of custom attributes that are returned in the REST response in addition to the\nstandard attributes the step already returns. The custom attributes defined here are only returned if the\nstep result does not lead to an error response.</p>\n<p>Custom attributes are added to the response when a step is initialized and when actions are executed on the step. They will therefore be available in the response leading to this step,\nand in any responses from endpoints specific to this step. For non-interactive steps, custom attributes are accumulated and added to the response leading to the next interactive step.</p>\n<p>Custom attributes are <i>not</i> returned for 'retrieve' endpoints.</p>\n",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<ValueMapProviderConfig> customResponseAttributes;
   @PluginListProperty(
      description = "A list of custom attributes that are added to the REST response in case of a step failure during step execution.\nUnexpected failures leading to HTTP response status code 500 do not contain these attributes.\n",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<ValueMapProviderConfig> customFailureResponseAttributes;

   @Override
   public Optional<StepId> getStepId() {
      return Optional.ofNullable(this.stepId).map(StepIdConfig::getId);
   }

   @Override
   public Optional<FlowConditionConfig> getSkipCondition() {
      return Optional.ofNullable(this.skipCondition);
   }

   @Override
   public Optional<FlowConditionConfig> getPreCondition() {
      return Optional.ofNullable(this.preCondition);
   }

   protected AbstractFlowStepConfig(
      @Nullable FlowConditionConfig skipCondition,
      @Nullable FlowConditionConfig preCondition,
      boolean requiresActivation,
      List<TagConfig> tagsOnSuccess,
      @Nullable StepIdConfig stepId,
      Map<String, StepIdConfig> onFailureGotos,
      List<ValueMapProviderConfig> customResponseAttributes,
      List<ValueMapProviderConfig> customFailureResponseAttributes
   ) {
      this.skipCondition = skipCondition;
      this.preCondition = preCondition;
      this.requiresActivation = requiresActivation;
      this.tagsOnSuccess = tagsOnSuccess;
      this.stepId = stepId;
      this.onFailureGotos = onFailureGotos;
      this.customResponseAttributes = customResponseAttributes;
      this.customFailureResponseAttributes = customFailureResponseAttributes;
   }

   @ConfigurationValidator
   static void validateStepIdPresentIfActivationRequired(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      PluginConfiguration stepId = PluginPropertyValueReader.effectivePluginPropertyValue(config, "stepId", env, context);
      if (stepId == null && requiresActivation(config, env, context)) {
         throw new PropertyConstraintViolationException("stepId", config, env, context, "If this step 'Requires Activation', a 'Step ID' must be configured.");
      }
   }

   private static boolean requiresActivation(PluginConfiguration config, ConfigurationEnvironment environment, ConfigurationContext context) throws PropertyConstraintViolationException {
      try {
         return BooleanPropertyValueReader.effectiveBooleanPropertyValue(config, "requiresActivation", environment, context);
      } catch (PropertyTypeMismatchException e) {
         return false;
      }
   }

   @Override
   public Map<String, StepIdConfig> getOnFailureGotos() {
      return this.onFailureGotos;
   }

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

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

   @Override
   public List<ValueMapProviderConfig> getCustomResponseAttributes() {
      return this.customResponseAttributes;
   }

   @Override
   public List<ValueMapProviderConfig> getCustomFailureResponseAttributes() {
      return this.customFailureResponseAttributes;
   }

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