package com.airlock.iam.flow.infrastructure.factories;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.flow.api.application.configuration.step.FlowStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.StepIdConfig;
import com.airlock.iam.flow.api.domain.model.flowcontrol.InteractiveGotoTarget;
import com.airlock.iam.flow.api.domain.model.step.StepId;
import com.airlock.iam.flow.domain.model.statemachine.spring.StepContext;
import com.google.common.annotations.VisibleForTesting;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

final class GotoFactory {
   private GotoFactory() {
   }

   public static List<GotoFactory.GotoPair> computeAllGotoPairs(StepContextCache stepContextCache) {
      Map<StepId, StepContext> idCache = buildIdCache(stepContextCache);
      return stepContextCache.getOrderedStepContexts().stream().flatMap(stepContext -> createPairs(stepContext, idCache)).toList();
   }

   private static Stream<GotoFactory.GotoPair> createPairs(StepContext stepContext, Map<StepId, StepContext> idCache) {
      FlowStepConfig stepConfig = stepContext.getConfig();
      return streamTargetStepIds(stepConfig)
         .map(id -> getTargetStepContext(id, idCache))
         .map(targetStepContext -> new GotoFactory.GotoPair(stepContext, targetStepContext));
   }

   @VisibleForTesting
   static Stream<StepId> streamTargetStepIds(FlowStepConfig stepConfig) {
      return Stream.concat(Stream.concat(streamInteractiveTargets(stepConfig), streamInternalTargets(stepConfig)), streamOnFailureTargets(stepConfig))
         .distinct()
         .sorted(Comparator.comparing(StepId::getStepId));
   }

   private static Stream<StepId> streamInteractiveTargets(FlowStepConfig stepConfig) {
      return stepConfig.getInteractiveGotoTargets().stream().map(InteractiveGotoTarget::getTargetStepId);
   }

   private static Stream<StepId> streamInternalTargets(FlowStepConfig stepConfig) {
      return stepConfig.getInternalGotoTargets().stream().map(StepIdConfig::getId);
   }

   private static Stream<StepId> streamOnFailureTargets(FlowStepConfig stepConfig) {
      return stepConfig.getOnFailureGotos().values().stream().map(StepIdConfig::getId);
   }

   private static StepContext getTargetStepContext(StepId id, Map<StepId, StepContext> idCache) {
      if (!idCache.containsKey(id)) {
         throw new FailureException("Goto target step ID " + LogUtil.quote(id.getStepId()) + " does not exist.");
      } else {
         return idCache.get(id);
      }
   }

   private static Map<StepId, StepContext> buildIdCache(StepContextCache stepContextCache) {
      Map<StepId, StepContext> map = new HashMap<>();

      for (StepContext stepContext : stepContextCache.getOrderedStepContexts()) {
         stepContext.getConfig().getStepId().ifPresent(id -> map.put(id, stepContext));
      }

      return map;
   }

   record GotoPair(StepContext origin, StepContext target) {
   }
}
