package com.airlock.iam.flow.shared.domain.model.step;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.session.FlowAttributeRepository;
import com.airlock.iam.flow.api.domain.model.step.FlowStep;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.shared.api.domain.model.valueprovider.ValueMapProvider;
import com.airlock.iam.flow.shared.application.configuration.step.ScriptableStepConfig;
import com.airlock.iam.flow.shared.domain.model.attributes.ScriptExecutionResultFlowAttribute;
import com.airlock.iam.scriptable.domain.service.ScriptError;
import com.airlock.iam.scriptable.domain.service.ScriptService;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Map;
import java.util.Objects;
import java.util.Map.Entry;
import java.util.stream.Collectors;

@LogicForConfig(ScriptableStepConfig.class)
public final class ScriptableStep implements FlowStep {
   private final Provider<ValueMapProvider> valueProviders;
   private final Provider<ScriptService> service;
   private final Provider<FlowAttributeRepository> flowAttributeRepository;
   private static final ELogger LOG = ELoggerFactory.getLogger(ScriptableStep.class);

   @Inject
   public ScriptableStep(Provider<ValueMapProvider> valueProviders, Provider<ScriptService> service, Provider<FlowAttributeRepository> flowAttributeRepository) {
      this.valueProviders = valueProviders;
      this.service = service;
      this.flowAttributeRepository = flowAttributeRepository;
   }

   @Override
   public StepResult initialize() {
      Result<Map<String, Object>, ScriptError> result = ((ScriptService)this.service.get())
         .executeScript(removeNullValues(((ValueMapProvider)this.valueProviders.get()).provideValueMap()));
      if (result.isFailure()) {
         LOG.error(EDefaultLogType.GENERIC_LOG_TYPE, buildErrorMessage(result));
         return this.stepResultFactory().internalError().build();
      } else {
         ((FlowAttributeRepository)this.flowAttributeRepository.get())
            .putOrMergeAttribute(ScriptExecutionResultFlowAttribute.scriptExecutionResultFlowAttribute(result.get()));
         return this.stepResultFactory().success().build();
      }
   }

   private static Map<String, Object> removeNullValues(Map<String, Object> map) {
      return map.entrySet().stream().filter(e -> !Objects.isNull(e.getValue())).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
   }

   private static String buildErrorMessage(Result<Map<String, Object>, ScriptError> result) {
      String exitCodeMessage = result.getFailure().getExitCode().map(c -> " with exit code " + c).orElse("");
      return "Scriptable Step failed" + exitCodeMessage + ": " + result.getFailure().getErrorMessage();
   }

   @Override
   public String getDescription() {
      return "Scriptable Step";
   }
}
