package com.airlock.iam.authentication.domain.service.secretquestions;

import com.airlock.iam.base.api.domain.model.validation.GenericError;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.api.domain.model.validation.ValidatorError;
import com.airlock.iam.common.application.configuration.secretquestion.GeneralSecretQuestionsSettings;
import com.airlock.iam.common.domain.model.token.secretquestions.SecretQuestion;
import com.airlock.iam.common.domain.model.token.secretquestions.SecretQuestionAnswer;
import com.airlock.iam.common.domain.model.token.secretquestions.SecretQuestionsRepository;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class SecretQuestionAnswerValidatorImpl implements SecretQuestionAnswerValidator {
   private final GeneralSecretQuestionsSettings settings;
   private final SecretQuestionHashService hashService;
   private final SecretQuestionsRepository repository;

   @Inject
   private SecretQuestionAnswerValidatorImpl(
      GeneralSecretQuestionsSettings settings, SecretQuestionHashService hashService, SecretQuestionsRepository repository
   ) {
      this.settings = settings;
      this.hashService = hashService;
      this.repository = repository;
   }

   @Override
   public ValidationResult validate(SecretQuestionAnswer answer) {
      return this.validate(answer, Collections.emptyList());
   }

   @Override
   public ValidationResult validate(SecretQuestionAnswer answer, List<SecretQuestionAnswer> notPersistedAnswers) {
      Optional<ValidatorError.ValidatorErrorBuilder> errorBuilder = this.checkAnswerPresent(answer)
         .or(() -> this.checkQuestionKnown(answer))
         .or(() -> this.checkDuplicateAnswer(answer, notPersistedAnswers));
      if (errorBuilder.isEmpty()) {
         String normalizedAnswer = this.settings.getNormalization().normalize(answer.getAnswer());
         errorBuilder = errorBuilder.or(() -> this.checkMinLength(normalizedAnswer))
            .or(() -> this.checkMaxLength(normalizedAnswer))
            .or(() -> this.checkRegex(normalizedAnswer));
      }

      return errorBuilder.<ValidatorError>map(builder -> builder.field(answer.getQuestionResourceKey()).build())
         .map(ValidationResult::invalid)
         .orElse(ValidationResult.valid());
   }

   private Optional<ValidatorError.ValidatorErrorBuilder> checkAnswerPresent(SecretQuestionAnswer answer) {
      if (answer.getAnswer() == null) {
         return Optional.of(ValidatorError.validatorErrorBuilder(GenericError.NOT_NULL));
      } else {
         return !answer.isSet() ? Optional.of(ValidatorError.validatorErrorBuilder(GenericError.REQUIRED)) : Optional.empty();
      }
   }

   private Optional<ValidatorError.ValidatorErrorBuilder> checkQuestionKnown(SecretQuestionAnswer answer) {
      return !this.settings.getQuestionResourceKeys().contains(answer.getQuestionResourceKey())
         ? Optional.of(ValidatorError.validatorErrorBuilder(GenericError.NOT_FOUND))
         : Optional.empty();
   }

   private Optional<ValidatorError.ValidatorErrorBuilder> checkDuplicateAnswer(SecretQuestionAnswer answer, List<SecretQuestionAnswer> notPersistedAnswers) {
      return this.isForbiddenDuplicate(answer, notPersistedAnswers)
         ? Optional.of(ValidatorError.validatorErrorBuilder(GenericError.CONFLICT))
         : Optional.empty();
   }

   private Optional<ValidatorError.ValidatorErrorBuilder> checkMinLength(String normalizedAnswer) {
      return normalizedAnswer.length() < this.settings.getMinLength()
         ? Optional.of(
            ValidatorError.validatorErrorBuilder(GenericError.MIN_LENGTH)
               .parameter("minLength", this.settings.getMinLength())
               .parameter("actualLength", normalizedAnswer.length())
         )
         : Optional.empty();
   }

   private Optional<ValidatorError.ValidatorErrorBuilder> checkMaxLength(String normalizedAnswer) {
      return normalizedAnswer.length() > this.settings.getMaxLength()
         ? Optional.of(
            ValidatorError.validatorErrorBuilder(GenericError.MAX_LENGTH)
               .parameter("maxLength", this.settings.getMaxLength())
               .parameter("actualLength", normalizedAnswer.length())
         )
         : Optional.empty();
   }

   private Optional<ValidatorError.ValidatorErrorBuilder> checkRegex(String normalizedAnswer) {
      Optional<Pattern> pattern = this.settings.getAnswerRegexPattern();
      return pattern.isPresent() && !pattern.get().matcher(normalizedAnswer).matches()
         ? Optional.of(ValidatorError.validatorErrorBuilder(GenericError.WRONG_FORMAT))
         : Optional.empty();
   }

   private boolean isForbiddenDuplicate(SecretQuestionAnswer answer, List<SecretQuestionAnswer> notPersistedAnswers) {
      if (!this.settings.areDuplicateAnswersForbidden()) {
         return false;
      }

      for (SecretQuestion existingSecretQuestion : Stream.concat(
            this.repository.query().forUser(answer.getUserId()).getResults().stream(),
            notPersistedAnswers.stream().map(this.hashService::convertToSecretQuestion)
         )
         .filter(secretQuestion -> !secretQuestion.getQuestionResourceKey().equals(answer.getQuestionResourceKey()))
         .collect(Collectors.toList())) {
         if (existingSecretQuestion.isEqualAnswer(answer.getAnswer(), this.settings.isCheckUsingLatin1Encoding(), this.settings.getHashFunctionPlugin())) {
            return true;
         }
      }

      return false;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class SecretQuestionAnswerValidatorImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private GeneralSecretQuestionsSettings settings;
      @SuppressFBWarnings(justification = "generated code")
      private SecretQuestionHashService hashService;
      @SuppressFBWarnings(justification = "generated code")
      private SecretQuestionsRepository repository;

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

      @SuppressFBWarnings(justification = "generated code")
      public SecretQuestionAnswerValidatorImpl.SecretQuestionAnswerValidatorImplBuilder settings(GeneralSecretQuestionsSettings settings) {
         this.settings = settings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SecretQuestionAnswerValidatorImpl.SecretQuestionAnswerValidatorImplBuilder hashService(SecretQuestionHashService hashService) {
         this.hashService = hashService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SecretQuestionAnswerValidatorImpl.SecretQuestionAnswerValidatorImplBuilder repository(SecretQuestionsRepository repository) {
         this.repository = repository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SecretQuestionAnswerValidatorImpl build() {
         return new SecretQuestionAnswerValidatorImpl(this.settings, this.hashService, this.repository);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "SecretQuestionAnswerValidatorImpl.SecretQuestionAnswerValidatorImplBuilder(settings="
            + this.settings
            + ", hashService="
            + this.hashService
            + ", repository="
            + this.repository
            + ")";
      }
   }
}
