package com.airlock.iam.flow.domain.model.dynamicactivation;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.application.configuration.dynamicactivation.DynamicStepActivationConfig;
import com.airlock.iam.flow.api.application.configuration.step.AbstractInteractiveFlowStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.FlowStepConfig;
import com.airlock.iam.flow.api.domain.model.session.FlowAttributeRepository;
import com.airlock.iam.flow.api.domain.model.step.StepAction;
import com.airlock.iam.flow.api.domain.model.step.StepError;
import com.airlock.iam.flow.api.domain.model.step.StepId;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.api.domain.model.step.StepResultFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Provider;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public final class DynamicStepActivationStepAdapter implements DynamicStepActivationStep {
   private static final ELogger LOG = ELoggerFactory.getLogger(DynamicStepActivationStepAdapter.class);
   private final StepResultFactory stepResultFactory;
   private final FlowStepConfig stepConfig;
   private final StepAction stepAction;
   private final String originalStepDescription;
   private final Provider<FlowAttributeRepository> attributes;

   @Override
   public StepResult retrieveDynamicStepActivationInformation() {
      return this.stepResultFactory.stay(this.stepAction).resultData(new DynamicStepActivationCollection(this.collectInformation())).build();
   }

   @Override
   public StepResult activateStep(StepId stepId) {
      Optional<DynamicStepActivationInformation> info = this.getActivation(stepId);
      if (info.isEmpty()) {
         return this.stepResultFactory.failRetry(DynamicStepActivationStepAdapter.DynamicActivationError.INVALID_STEP_ID, this.stepAction).build();
      }

      if (!info.get().isActivatable()) {
         LOG.info("Cannot activate step " + LogUtil.quote(stepId) + " on this step.");
         return this.stepResultFactory.failRetry(DynamicStepActivationStepAdapter.DynamicActivationError.STEP_ACTIVATION_NOT_ALLOWED, this.stepAction).build();
      }

      if (!info.get().isActivated()) {
         this.updateState(this.getState().withActivatedStep(stepId));
         LOG.info("Activating step " + LogUtil.quote(stepId));
      }

      return this.stepResultFactory.stay(this.stepAction).build();
   }

   @Override
   public StepResult deactivateStep(StepId stepId) {
      Optional<DynamicStepActivationInformation> info = this.getActivation(stepId);
      if (info.isEmpty()) {
         return this.stepResultFactory.failRetry(DynamicStepActivationStepAdapter.DynamicActivationError.INVALID_STEP_ID, this.stepAction).build();
      }

      if (!info.get().isDeactivatable()) {
         LOG.info("Cannot deactivate step " + LogUtil.quote(stepId) + " on this step.");
         return this.stepResultFactory
            .failRetry(DynamicStepActivationStepAdapter.DynamicActivationError.STEP_DEACTIVATION_NOT_ALLOWED, this.stepAction)
            .build();
      }

      if (info.get().isActivated()) {
         this.updateState(this.getState().withDeactivatedStep(stepId));
         LOG.info("Deactivating step " + LogUtil.quote(stepId));
      }

      return this.stepResultFactory.stay(this.stepAction).build();
   }

   private DynamicStepActivationState getState() {
      return ((FlowAttributeRepository)this.attributes.get()).tryGetAttribute(DynamicStepActivationState.class).orElseGet(DynamicStepActivationState::new);
   }

   private Optional<DynamicStepActivationInformation> getActivation(StepId stepId) {
      return this.collectInformation().stream().filter(info -> info.getStepId().equals(stepId)).findFirst();
   }

   private List<DynamicStepActivationInformation> collectInformation() {
      DynamicStepActivationState state = this.getState();
      return ((AbstractInteractiveFlowStepConfig)this.stepConfig)
         .getDynamicStepActivations()
         .stream()
         .map(config -> this.toActivationInformation(config, state))
         .collect(Collectors.toList());
   }

   private DynamicStepActivationInformation toActivationInformation(DynamicStepActivationConfig config, DynamicStepActivationState state) {
      return DynamicStepActivationInformation.builder()
         .stepId(config.getTargetStepId())
         .activatable(config.isActivatable())
         .deactivatable(config.isDeactivatable())
         .activated(state.getActivatedSteps().contains(config.getTargetStepId()))
         .build();
   }

   private void updateState(DynamicStepActivationState newState) {
      ((FlowAttributeRepository)this.attributes.get()).putOrMergeAttribute(newState);
   }

   @Override
   public StepResult initialize() {
      throw new UnsupportedOperationException("should never be called");
   }

   @Override
   public String getDescription() {
      return "Dynamic Step Activation Adapter for " + this.originalStepDescription;
   }

   @Override
   public StepResultFactory stepResultFactory() {
      return this.stepResultFactory;
   }

   @SuppressFBWarnings(justification = "generated code")
   public DynamicStepActivationStepAdapter(
      StepResultFactory stepResultFactory,
      FlowStepConfig stepConfig,
      StepAction stepAction,
      String originalStepDescription,
      Provider<FlowAttributeRepository> attributes
   ) {
      this.stepResultFactory = stepResultFactory;
      this.stepConfig = stepConfig;
      this.stepAction = stepAction;
      this.originalStepDescription = originalStepDescription;
      this.attributes = attributes;
   }

   public enum DynamicActivationError implements StepError {
      INVALID_STEP_ID,
      STEP_ACTIVATION_NOT_ALLOWED,
      STEP_DEACTIVATION_NOT_ALLOWED;
   }
}
