package com.airlock.iam.plugin.configeditor.ui.flowviz;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.flow.api.application.configuration.selection.SelectionFlowStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.AbstractInteractiveFlowStepConfig;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.plugin.api.exception.PropertyConfigurationException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.BooleanPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginListPropertyValueReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public final class FlowVisualizerService {
   private final ConfigurationEnvironment environment;
   private final ConfigurationContext context;

   public FlowVisualizerService(ConfigurationEnvironment environment, ConfigurationContext context) {
      this.environment = environment;
      this.context = context;
   }

   public FlowVisualizationModel createModel(PluginConfiguration flowConfig) {
      List<FlowVisualizationModel.Step> steps = this.getPluginList(flowConfig, "steps").stream().map(this::toStep).toList();
      FlowVisualizationModel.Flow flow = new FlowVisualizationModel.Flow(steps);
      return new FlowVisualizationModel(flowConfig.getUuid(), getDisplayNameOrClassName(flowConfig), this.environment, this.context, flow);
   }

   private List<PluginConfiguration> getPluginList(PluginConfiguration parent, String property) {
      try {
         return PluginListPropertyValueReader.effectivePluginListPropertyValue(parent, property, this.environment, this.context);
      } catch (PropertyTypeMismatchException e) {
         return Collections.emptyList();
      }
   }

   private List<FlowVisualizationModel.SelectionOption> optionsOfSelection(List<PluginConfiguration> availableOptions) throws PropertyTypeMismatchException {
      List<FlowVisualizationModel.SelectionOption> optionsOfSelection = new ArrayList<>();

      for (PluginConfiguration option : availableOptions) {
         List<PluginConfiguration> optionSteps = this.getPluginList(option, "steps");
         List<FlowVisualizationModel.Step> steps = optionSteps.stream().map(this::toStep).toList();
         Optional<String> options = this.getOptionCondition(option);
         if (options.isPresent()) {
            optionsOfSelection.add(
               new FlowVisualizationModel.SelectionOption(getDisplayNameOrClassName(option), options.get(), new FlowVisualizationModel.Flow(steps))
            );
         } else {
            optionsOfSelection.add(new FlowVisualizationModel.SelectionOption(getDisplayNameOrClassName(option), new FlowVisualizationModel.Flow(steps)));
         }
      }

      return optionsOfSelection;
   }

   private Optional<String> getOptionCondition(PluginConfiguration configuration) throws PropertyTypeMismatchException {
      return Optional.ofNullable(configuration.getPluginPropertyConfigurationValue("condition", this.environment, this.context))
         .map(FlowVisualizerService::getDisplayNameOrClassName);
   }

   private Optional<String> getSkipCondition(PluginConfiguration configuration) throws PropertyTypeMismatchException {
      return Optional.ofNullable(configuration.getPluginPropertyConfigurationValue("skipCondition", this.environment, this.context))
         .map(FlowVisualizerService::getDisplayNameOrClassName);
   }

   private Optional<String> getPreCondition(PluginConfiguration configuration) throws PropertyTypeMismatchException {
      return Optional.ofNullable(configuration.getPluginPropertyConfigurationValue("preCondition", this.environment, this.context))
         .map(FlowVisualizerService::getDisplayNameOrClassName);
   }

   private List<PluginConfiguration> getTagsOnSuccess(PluginConfiguration configuration) {
      return Optional.ofNullable(this.getPluginList(configuration, "tagsOnSuccess")).orElse(Collections.emptyList());
   }

   private Optional<String> getStepId(PluginConfiguration configuration) throws PropertyTypeMismatchException {
      return Optional.ofNullable(configuration.getPluginPropertyConfigurationValue("stepId", this.environment, this.context)).map(this::extractId);
   }

   private List<String> getGotoTargets(PluginConfiguration configuration) throws PropertyTypeMismatchException {
      return this.getStepIdsFor(configuration, "interactiveGotoTargets", "targetStepId");
   }

   private List<String> getDynamicStepActivations(PluginConfiguration configuration) {
      return this.getStepIdsFor(configuration, "dynamicStepActivations", "targetStepId");
   }

   private List<String> getStepIdsFor(PluginConfiguration configuration, String pluginProperty, String stepIdProperty) {
      return this.getPluginList(configuration, pluginProperty)
         .stream()
         .map(interactiveGotoTarget -> this.extractTargetStepId(interactiveGotoTarget, stepIdProperty))
         .flatMap(Optional::stream)
         .toList();
   }

   private boolean isActivationRequired(PluginConfiguration configuration) {
      try {
         return BooleanPropertyValueReader.effectiveBooleanPropertyValue(configuration, "requiresActivation", this.environment, this.context);
      } catch (PropertyConfigurationException var3) {
         return false;
      }
   }

   private Optional<String> extractTargetStepId(PluginConfiguration interactiveGotoTarget, String stepIdPluginPropertyName) {
      try {
         PluginConfiguration target = interactiveGotoTarget.getPluginPropertyConfigurationValue(stepIdPluginPropertyName, this.environment, this.context);
         if (target != null) {
            return Optional.ofNullable(this.extractId(target));
         }
      } catch (PropertyTypeMismatchException var4) {
      }

      return Optional.empty();
   }

   private FlowVisualizationModel.Step toStep(PluginConfiguration configuration) {
      try {
         String stepName = getDisplayNameOrClassName(configuration);
         Optional<String> preCondition = this.getPreCondition(configuration);
         Optional<String> skipCondition = this.getSkipCondition(configuration);
         List<String> tagsOnSucces = this.getTagsOnSuccess(configuration)
            .stream()
            .map(FlowVisualizerService::getDisplayNameOrClassName)
            .collect(Collectors.toList());
         Optional<String> stepId = this.getStepId(configuration);
         List<String> gotoTargets = this.getGotoTargets(configuration);
         List<String> dynamicStepActivations = this.getDynamicStepActivations(configuration);
         boolean requiresActivation = this.isActivationRequired(configuration);
         boolean interactive = isInteractive(configuration);
         if (SelectionFlowStepConfig.class.isAssignableFrom(configuration.getPluginClass())) {
            List<PluginConfiguration> optionList = this.getPluginList(configuration, "availableOptions");
            if (optionList.isEmpty()) {
               throw new InitializeException("Invalid Config: Selection Options can't be empty");
            }

            List<PluginConfiguration> fallbackFlow = this.getPluginList(configuration, "fallbackFlow");
            List<FlowVisualizationModel.SelectionOption> options = this.optionsOfSelection(optionList);
            return new FlowVisualizationModel.SelectionStep(
               stepName,
               options,
               this.fallbackFlow(fallbackFlow),
               interactive,
               preCondition,
               skipCondition,
               stepId,
               gotoTargets,
               dynamicStepActivations,
               requiresActivation
            );
         } else {
            return new FlowVisualizationModel.Step(
               stepName, interactive, preCondition, skipCondition, tagsOnSucces, stepId, gotoTargets, dynamicStepActivations, requiresActivation
            );
         }
      } catch (InitializeException exception) {
         throw new FailureException("Exception triggered by " + configuration.getPluginClassDisplayName() + ": " + exception.getMessage(), exception);
      }
   }

   private Optional<FlowVisualizationModel.Flow> fallbackFlow(List<PluginConfiguration> fallbackFlow) {
      List<FlowVisualizationModel.Step> steps = fallbackFlow.stream().map(this::toStep).toList();
      return steps.isEmpty() ? Optional.empty() : Optional.of(new FlowVisualizationModel.Flow(steps));
   }

   private static boolean isInteractive(PluginConfiguration stepConfig) {
      try {
         return AbstractInteractiveFlowStepConfig.class.isAssignableFrom(stepConfig.getPluginClass());
      } catch (InitializeException e) {
         return false;
      }
   }

   private static String getDisplayNameOrClassName(PluginConfiguration pluginConfiguration) {
      return pluginConfiguration.getPluginDisplayName() != null ? pluginConfiguration.getPluginDisplayName() : pluginConfiguration.getPluginClassDisplayName();
   }

   private String extractId(PluginConfiguration value) {
      try {
         return value.getSimplePropertyConfigurationValue("id", this.environment, this.context);
      } catch (PropertyTypeMismatchException e) {
         throw new FailureException(e);
      }
   }
}
