package com.airlock.iam.authentication.impl.domain.model.step;

import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResult;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultBuilder;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.common.api.domain.model.authentication.factor.AuthenticationFactor;
import com.airlock.iam.common.api.domain.model.authentication.factor.AuthenticationFactorDetail;
import com.airlock.iam.common.api.domain.model.device.usage.AuthenticationDeviceUsage;
import com.airlock.iam.flow.api.domain.model.step.AdditionalAttributes;
import com.airlock.iam.flow.api.domain.model.step.InternalStepResultData;
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.airlock.iam.flow.api.domain.model.tag.Tag;
import com.airlock.iam.flow.impl.domain.model.step.DefaultStepResultBuilder;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

public final class AuthenticationStepResultBuilderImpl implements AuthenticationStepResultBuilder {
   private final DefaultStepResultBuilder defaultStepResultBuilder;
   private boolean isFailedFactor = true;
   private Optional<AuthenticationFactor> factor = Optional.empty();
   private Optional<AuthenticationFactorDetail> factorDetail = Optional.empty();
   private Optional<AuthenticationDeviceUsage> deviceUsageData = Optional.empty();

   public static AuthenticationStepResultBuilderImpl authenticationStepResultBuilder(StepResult.StepResultType type) {
      return new AuthenticationStepResultBuilderImpl(type);
   }

   private AuthenticationStepResultBuilderImpl(StepResult.StepResultType type) {
      this.defaultStepResultBuilder = DefaultStepResultBuilder.defaultStepResultBuilder(type);
   }

   @Override
   public AuthenticationStepResultBuilder type(StepResult.StepResultType value) {
      this.defaultStepResultBuilder.type(value);
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder nextAction(StepAction nextAction) {
      this.defaultStepResultBuilder.nextAction(nextAction);
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder error(StepError error) {
      this.defaultStepResultBuilder.error(error);
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder obtainedTags(Set<Tag> obtainedTags) {
      this.defaultStepResultBuilder.obtainedTags(obtainedTags);
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder addObtainedTags(Set<Tag> additionalObtainedTags) {
      this.defaultStepResultBuilder.addObtainedTags(additionalObtainedTags);
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder resultData(StepResultData resultData) {
      this.defaultStepResultBuilder.resultData(resultData);
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder withoutInternalResultData() {
      this.defaultStepResultBuilder.withoutInternalResultData();
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder internalResultData(InternalStepResultData internalResultData) {
      this.defaultStepResultBuilder.internalResultData(internalResultData);
      return this;
   }

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

   @Override
   public AuthenticationStepResultBuilder addMetadataItem(String key, Object value) {
      this.defaultStepResultBuilder.addMetadataItem(key, value);
      return this;
   }

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

   @Override
   public AuthenticationStepResultBuilder addAdditionalAttribute(String key, Object value) {
      this.defaultStepResultBuilder.addAdditionalAttribute(key, value);
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder factor(AuthenticationFactor newFactor) {
      this.factorDetail
         .ifPresent(
            detail -> FailureException.checkTrue(
               detail.getFactor().equals(newFactor),
               String.format("Authentication factor '%s' contradicts existing factor '%s' found in factor details.", newFactor, detail.getFactor())
            )
         );
      this.factor = Optional.of(newFactor);
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder factorDetail(AuthenticationFactorDetail newFactorDetail) {
      this.factorDetail = Optional.of(newFactorDetail);
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder isFailedFactor(boolean failed) {
      this.isFailedFactor = failed;
      return this;
   }

   @Override
   public AuthenticationStepResultBuilder deviceUsageData(AuthenticationDeviceUsage usageData) {
      this.deviceUsageData = Optional.of(usageData);
      return this;
   }

   @Override
   public AuthenticationStepResult build() {
      Optional<AuthenticationFactor> effectiveFactor = this.getEffectiveFactor();
      FailureException.checkTrue(effectiveFactor.isPresent(), "Cannot build authentication step result without authentication factor or factor details.");
      return new AuthenticationStepResultImpl(
         this.defaultStepResultBuilder.build(), effectiveFactor.get(), this.factorDetail, this.isFailedFactor, this.deviceUsageData
      );
   }

   private Optional<AuthenticationFactor> getEffectiveFactor() {
      if (this.factorDetail.isPresent()) {
         return Optional.of(this.factorDetail.get().getFactor());
      } else {
         return this.factor.isPresent() ? this.factor : Optional.empty();
      }
   }
}
