package com.airlock.iam.transactionapproval.domain.model.parameter;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.api.domain.model.validation.ValidatorError;
import com.airlock.iam.flow.api.domain.model.session.FlowSession;
import com.airlock.iam.flow.api.domain.model.step.GenericStepError;
import com.airlock.iam.flow.api.domain.model.step.StepAction;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.shared.application.configuration.HttpParameterSpec;
import com.airlock.iam.flow.shared.domain.model.attributes.AuthTokenIdAttribute;
import com.airlock.iam.log.api.signing.SigningLogger;
import com.airlock.iam.login.domain.model.credential.RequestAttributeExtractor;
import com.airlock.iam.login.domain.model.credential.RequestAttributeExtractor.AttributeType;
import com.airlock.iam.transactionapproval.application.configuration.parameter.TransactionApprovalParameterStepConfig;
import com.airlock.iam.transactionapproval.domain.model.flow.TransactionApprovalMessageAttributes;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@LogicForConfig(TransactionApprovalParameterStepConfig.class)
public class TransactionApprovalParameterStepImpl implements TransactionApprovalParameterStep {
   private final Provider<FlowSession> flowSession;
   private final Provider<List<HttpParameterSpec>> parameterSpecs;
   private final Provider<SigningLogger> signingLogger;

   @Inject
   public TransactionApprovalParameterStepImpl(
      Provider<FlowSession> flowSession, Provider<List<HttpParameterSpec>> parameterSpecs, Provider<SigningLogger> signingLogger
   ) {
      this.flowSession = flowSession;
      this.parameterSpecs = parameterSpecs;
      this.signingLogger = signingLogger;
   }

   @Override
   public StepResult validateAndConsume(Map<String, String> messageParameters, Optional<String> authTokenId) {
      RequestAttributeExtractor extractor = this.createExtractor();
      Set<String> unexpectedParameters = extractor.collectUnexpectedParameters(messageParameters, AttributeType.BODY);
      if (!unexpectedParameters.isEmpty()) {
         this.logUnexpectedParametersIfPresent(unexpectedParameters);
         return this.stepResultFactory()
            .failRetry(GenericStepError.VALIDATION_FAILED, TransactionApprovalParameterStepImpl.TransactionApprovalParameterStepActions.PARAMETERS_REQUIRED)
            .build();
      }

      ValidationResult validationResult = this.validateParameters(extractor, messageParameters);
      if (!validationResult.isValid()) {
         this.logInvalidParameters(validationResult);
         return this.stepResultFactory()
            .failRetry(GenericStepError.VALIDATION_FAILED, TransactionApprovalParameterStepImpl.TransactionApprovalParameterStepActions.PARAMETERS_REQUIRED)
            .build();
      }

      ((FlowSession)this.flowSession.get())
         .flowAttributes()
         .putOrMergeAttribute(TransactionApprovalMessageAttributes.transactionApprovalMessageAttributes(messageParameters));
      if (authTokenId.isPresent()) {
         ((FlowSession)this.flowSession.get()).flowAttributes().putOrMergeAttribute(AuthTokenIdAttribute.authTokenIdAttribute(authTokenId.get()));
      }

      return this.stepResultFactory().success().build();
   }

   private void logUnexpectedParametersIfPresent(Set<String> unknownParameters) {
      if (!unknownParameters.isEmpty()) {
         ((SigningLogger)this.signingLogger.get())
            .log("Validation failed because the request contained the following unknown message parameters: " + unknownParameters.toString() + ".");
      }
   }

   private void logInvalidParameters(ValidationResult validationResult) {
      String invalidFields = validationResult.getErrors().stream().<CharSequence>map(ValidatorError::getField).collect(Collectors.joining(", "));
      ((SigningLogger)this.signingLogger.get())
         .log("Validation failed because the following request parameters have invalid values or exceed the maximal length: " + invalidFields + ".");
   }

   public StepResult initialize() {
      return this.stepResultFactory().stay(TransactionApprovalParameterStepImpl.TransactionApprovalParameterStepActions.PARAMETERS_REQUIRED).build();
   }

   public String getDescription() {
      return "Transaction-Approval Parameter Step";
   }

   private RequestAttributeExtractor createExtractor() {
      return RequestAttributeExtractor.builder().bodyParameterSpec((List)this.parameterSpecs.get()).headerParameterSpec(Collections.emptyList()).build();
   }

   private ValidationResult validateParameters(RequestAttributeExtractor extractor, Map<String, String> messageParameters) {
      return extractor.validateAttributes(messageParameters, AttributeType.BODY);
   }

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

   public enum TransactionApprovalParameterStepActions implements StepAction {
      PARAMETERS_REQUIRED;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class TransactionApprovalParameterStepImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Provider<FlowSession> flowSession;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<List<HttpParameterSpec>> parameterSpecs;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<SigningLogger> signingLogger;

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

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalParameterStepImpl.TransactionApprovalParameterStepImplBuilder flowSession(Provider<FlowSession> flowSession) {
         this.flowSession = flowSession;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalParameterStepImpl.TransactionApprovalParameterStepImplBuilder parameterSpecs(Provider<List<HttpParameterSpec>> parameterSpecs) {
         this.parameterSpecs = parameterSpecs;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalParameterStepImpl.TransactionApprovalParameterStepImplBuilder signingLogger(Provider<SigningLogger> signingLogger) {
         this.signingLogger = signingLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TransactionApprovalParameterStepImpl build() {
         return new TransactionApprovalParameterStepImpl(this.flowSession, this.parameterSpecs, this.signingLogger);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "TransactionApprovalParameterStepImpl.TransactionApprovalParameterStepImplBuilder(flowSession="
            + this.flowSession
            + ", parameterSpecs="
            + this.parameterSpecs
            + ", signingLogger="
            + this.signingLogger
            + ")";
      }
   }
}
