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

import com.airlock.iam.flow.api.domain.model.flow.FlowResult;
import com.airlock.iam.flow.api.domain.model.step.AdditionalAttributes;
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.StepResult;
import com.airlock.iam.flow.api.domain.model.step.StepResultData;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Map;
import java.util.Optional;
import lombok.NonNull;

public final class FlowResultImpl implements FlowResult {
   private final boolean flowCompletedSuccessfully;
   private final Optional<StepAction> nextStep;
   private final Optional<StepError> error;
   private final Optional<StepResultData> data;
   private final Map<String, Object> metadata;
   private final AdditionalAttributes additionalAttributes;
   private final boolean internalError;

   private FlowResultImpl(
      boolean flowCompletedSuccessfully,
      @NonNull Optional<StepAction> nextStep,
      @NonNull Optional<StepError> error,
      @NonNull Optional<StepResultData> data,
      @NonNull Map<String, Object> metadata,
      @NonNull AdditionalAttributes additionalAttributes,
      boolean internalError
   ) {
      if (nextStep == null) {
         throw new NullPointerException("nextStep is marked non-null but is null");
      }

      if (error == null) {
         throw new NullPointerException("error is marked non-null but is null");
      }

      if (data == null) {
         throw new NullPointerException("data is marked non-null but is null");
      }

      if (metadata == null) {
         throw new NullPointerException("metadata is marked non-null but is null");
      }

      if (additionalAttributes == null) {
         throw new NullPointerException("additionalAttributes is marked non-null but is null");
      }

      this.flowCompletedSuccessfully = flowCompletedSuccessfully;
      this.nextStep = nextStep;
      this.error = error;
      this.data = data;
      this.metadata = metadata;
      this.additionalAttributes = additionalAttributes;
      this.internalError = internalError;
   }

   @Override
   public <T extends StepResultData> Optional<T> data() {
      return (Optional<T>)this.data;
   }

   @Override
   public Map<String, Object> metadata() {
      return this.metadata;
   }

   private static FlowResultImpl.FlowResultImplBuilder successCompleted() {
      return builder().flowCompletedSuccessfully(true);
   }

   private static FlowResultImpl.FlowResultImplBuilder successStay(StepAction nextStep) {
      Preconditions.checkNotNull(nextStep);
      return builder().nextStep(Optional.of(nextStep));
   }

   private static FlowResultImpl.FlowResultImplBuilder failedRetry(StepAction nextStep, StepError error) {
      Preconditions.checkNotNull(error);
      Preconditions.checkNotNull(nextStep);
      return builder().nextStep(Optional.of(nextStep)).error(Optional.of(error));
   }

   private static FlowResultImpl.FlowResultImplBuilder failed(StepError error) {
      Preconditions.checkNotNull(error);
      return builder().error(Optional.of(error));
   }

   public static FlowResultImpl of(StepResult result) {
      FlowResultImpl.FlowResultImplBuilder builder;
      if (result.hasType(StepResult.StepResultType.SUCCESS) || result.hasType(StepResult.StepResultType.SKIP)) {
         builder = successCompleted();
      } else if (result.hasType(StepResult.StepResultType.STAY)) {
         builder = successStay(result.nextAction().orElseThrow());
      } else if (result.hasType(StepResult.StepResultType.FAIL_RETRY)) {
         builder = failedRetry(result.nextAction().orElseThrow(), result.error().orElseThrow());
      } else if (result.hasType(StepResult.StepResultType.INTERNAL_ERROR)) {
         builder = internalError(result.error());
      } else {
         builder = failed(result.error().orElseThrow());
      }

      return builder.data(result.resultData()).metadata(result.metadata()).additionalAttributes(result.additionalAttributes()).build();
   }

   private static FlowResultImpl.FlowResultImplBuilder internalError(Optional<StepError> error) {
      return builder().internalError(true).error(error);
   }

   public static FlowResultImpl withError(FlowResult result, StepError error) {
      FlowResultImpl.FlowResultImplBuilder builder;
      if (result.nextStep().isPresent()) {
         builder = failedRetry(result.nextStep().get(), error);
      } else {
         builder = failed(error);
      }

      return builder.data(result.data()).metadata(result.metadata()).additionalAttributes(result.additionalAttributes()).build();
   }

   @Override
   public AdditionalAttributes additionalAttributes() {
      return this.additionalAttributes;
   }

   public static FlowResultImpl.FlowResultImplBuilder builder() {
      return new FlowResultImpl.FlowResultImplBuilder()
         .nextStep(Optional.empty())
         .error(Optional.empty())
         .data(Optional.empty())
         .additionalAttributes(new AdditionalAttributes())
         .metadata(Map.of());
   }

   @Override
   public boolean isFailed() {
      return this.error.isPresent() || this.internalError;
   }

   @Override
   public boolean hasInternalError() {
      return this.internalError;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public boolean flowCompletedSuccessfully() {
      return this.flowCompletedSuccessfully;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public Optional<StepAction> nextStep() {
      return this.nextStep;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public Optional<StepError> error() {
      return this.error;
   }

   @SuppressFBWarnings(justification = "generated code")
   public boolean internalError() {
      return this.internalError;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public boolean equals(Object o) {
      if (o == this) {
         return true;
      } else if (!(o instanceof FlowResultImpl other)) {
         return false;
      } else {
         if (this.flowCompletedSuccessfully != other.flowCompletedSuccessfully) {
            return false;
         }

         if (this.internalError != other.internalError) {
            return false;
         }

         Object this$nextStep = this.nextStep;
         Object other$nextStep = other.nextStep;
         if (this$nextStep == null ? other$nextStep == null : this$nextStep.equals(other$nextStep)) {
            Object this$error = this.error;
            Object other$error = other.error;
            if (this$error == null ? other$error == null : this$error.equals(other$error)) {
               Object this$data = this.data;
               Object other$data = other.data;
               if (this$data == null ? other$data == null : this$data.equals(other$data)) {
                  Object this$metadata = this.metadata;
                  Object other$metadata = other.metadata;
                  if (this$metadata == null ? other$metadata == null : this$metadata.equals(other$metadata)) {
                     Object this$additionalAttributes = this.additionalAttributes;
                     Object other$additionalAttributes = other.additionalAttributes;
                     return this$additionalAttributes == null
                        ? other$additionalAttributes == null
                        : this$additionalAttributes.equals(other$additionalAttributes);
                  } else {
                     return false;
                  }
               } else {
                  return false;
               }
            } else {
               return false;
            }
         } else {
            return false;
         }
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public int hashCode() {
      int PRIME = 59;
      int result = 1;
      result = result * 59 + (this.flowCompletedSuccessfully ? 79 : 97);
      result = result * 59 + (this.internalError ? 79 : 97);
      Object $nextStep = this.nextStep;
      result = result * 59 + ($nextStep == null ? 43 : $nextStep.hashCode());
      Object $error = this.error;
      result = result * 59 + ($error == null ? 43 : $error.hashCode());
      Object $data = this.data;
      result = result * 59 + ($data == null ? 43 : $data.hashCode());
      Object $metadata = this.metadata;
      result = result * 59 + ($metadata == null ? 43 : $metadata.hashCode());
      Object $additionalAttributes = this.additionalAttributes;
      return result * 59 + ($additionalAttributes == null ? 43 : $additionalAttributes.hashCode());
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public String toString() {
      return "FlowResultImpl(flowCompletedSuccessfully="
         + this.flowCompletedSuccessfully()
         + ", nextStep="
         + this.nextStep()
         + ", error="
         + this.error()
         + ", data="
         + this.data()
         + ", metadata="
         + this.metadata()
         + ", additionalAttributes="
         + this.additionalAttributes()
         + ", internalError="
         + this.internalError()
         + ")";
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class FlowResultImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private boolean flowCompletedSuccessfully;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<StepAction> nextStep;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<StepError> error;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<StepResultData> data;
      @SuppressFBWarnings(justification = "generated code")
      private Map<String, Object> metadata;
      @SuppressFBWarnings(justification = "generated code")
      private AdditionalAttributes additionalAttributes;
      @SuppressFBWarnings(justification = "generated code")
      private boolean internalError;

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

      @SuppressFBWarnings(justification = "generated code")
      public FlowResultImpl.FlowResultImplBuilder flowCompletedSuccessfully(boolean flowCompletedSuccessfully) {
         this.flowCompletedSuccessfully = flowCompletedSuccessfully;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FlowResultImpl.FlowResultImplBuilder nextStep(@NonNull Optional<StepAction> nextStep) {
         if (nextStep == null) {
            throw new NullPointerException("nextStep is marked non-null but is null");
         }

         this.nextStep = nextStep;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FlowResultImpl.FlowResultImplBuilder error(@NonNull Optional<StepError> error) {
         if (error == null) {
            throw new NullPointerException("error is marked non-null but is null");
         }

         this.error = error;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FlowResultImpl.FlowResultImplBuilder data(@NonNull Optional<StepResultData> data) {
         if (data == null) {
            throw new NullPointerException("data is marked non-null but is null");
         }

         this.data = data;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FlowResultImpl.FlowResultImplBuilder metadata(@NonNull Map<String, Object> metadata) {
         if (metadata == null) {
            throw new NullPointerException("metadata is marked non-null but is null");
         }

         this.metadata = metadata;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FlowResultImpl.FlowResultImplBuilder additionalAttributes(@NonNull AdditionalAttributes additionalAttributes) {
         if (additionalAttributes == null) {
            throw new NullPointerException("additionalAttributes is marked non-null but is null");
         }

         this.additionalAttributes = additionalAttributes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FlowResultImpl.FlowResultImplBuilder internalError(boolean internalError) {
         this.internalError = internalError;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FlowResultImpl build() {
         return new FlowResultImpl(
            this.flowCompletedSuccessfully, this.nextStep, this.error, this.data, this.metadata, this.additionalAttributes, this.internalError
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "FlowResultImpl.FlowResultImplBuilder(flowCompletedSuccessfully="
            + this.flowCompletedSuccessfully
            + ", nextStep="
            + this.nextStep
            + ", error="
            + this.error
            + ", data="
            + this.data
            + ", metadata="
            + this.metadata
            + ", additionalAttributes="
            + this.additionalAttributes
            + ", internalError="
            + this.internalError
            + ")";
      }
   }
}
