package com.airlock.iam.flow.domain.model.statemachine.spring;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.flow.api.application.configuration.step.FlowStepConfig;
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.domain.model.event.StateMachineFlowEvent;
import com.airlock.iam.flow.domain.model.statemachine.FlowState;
import com.airlock.iam.flow.domain.model.statemachine.FlowStateMachine;
import com.airlock.iam.flow.domain.model.statemachine.StateMachineController;
import com.airlock.iam.flow.domain.model.statemachine.runtime.FlowEventFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineEventResult;
import org.springframework.statemachine.StateMachineEventResult.ResultType;
import reactor.core.publisher.Mono;

public final class SpringFlowStateMachine implements FlowStateMachine {
   private final StateMachine<FlowState, StateMachineFlowEvent> stateMachine;
   private final StateMachineController controller;

   @Override
   public FlowState getCurrentFlowState() {
      return (FlowState)this.stateMachine.getState().getId();
   }

   @Override
   public void processStepResult(StepResult stepResult) {
      Optional<FlowStep> step = this.getCurrentFlowState().getStep();
      if (step.isEmpty()) {
         throw new FailureException("Attempting to access step of a state that does not have one.");
      }

      FlowStepConfig stepConfig = this.getCurrentFlowState().getStepConfig().orElseThrow(() -> new FailureException("Current step must have a config"));
      StepResult newStepResult = this.controller.postProcessStepResult(stepResult, step.get(), stepConfig);
      StateMachineUtil.sendEvent(newStepResult, this.stateMachine);
      if (this.stateMachine.hasStateMachineError()) {
         throw new FailureException("An unexpected exception has occurred in the state machine. Aborting.");
      }
   }

   @Override
   public void start() {
      this.stateMachine.startReactively().block();
      StateMachineEventResult<FlowState, StateMachineFlowEvent> result = (StateMachineEventResult<FlowState, StateMachineFlowEvent>)this.stateMachine
         .sendEvent(Mono.just(FlowEventFactory.flowStartedEventMessage()))
         .blockLast();
      FailureException.checkTrue(result != null && result.getResultType() != ResultType.DENIED, "State machine could not start.");
      FailureException.checkTrue(!this.hasError(), "State machine is in an error state.");
   }

   @Override
   public boolean hasError() {
      return this.stateMachine.hasStateMachineError();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static SpringFlowStateMachine.SpringFlowStateMachineBuilder builder() {
      return new SpringFlowStateMachine.SpringFlowStateMachineBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public SpringFlowStateMachine(StateMachine<FlowState, StateMachineFlowEvent> stateMachine, StateMachineController controller) {
      this.stateMachine = stateMachine;
      this.controller = controller;
   }

   @SuppressFBWarnings(justification = "generated code")
   public StateMachine<FlowState, StateMachineFlowEvent> getStateMachine() {
      return this.stateMachine;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class SpringFlowStateMachineBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private StateMachine<FlowState, StateMachineFlowEvent> stateMachine;
      @SuppressFBWarnings(justification = "generated code")
      private StateMachineController controller;

      @SuppressFBWarnings(justification = "generated code")
      SpringFlowStateMachineBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public SpringFlowStateMachine.SpringFlowStateMachineBuilder stateMachine(StateMachine<FlowState, StateMachineFlowEvent> stateMachine) {
         this.stateMachine = stateMachine;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SpringFlowStateMachine.SpringFlowStateMachineBuilder controller(StateMachineController controller) {
         this.controller = controller;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SpringFlowStateMachine build() {
         return new SpringFlowStateMachine(this.stateMachine, this.controller);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "SpringFlowStateMachine.SpringFlowStateMachineBuilder(stateMachine=" + this.stateMachine + ", controller=" + this.controller + ")";
      }
   }
}
