package com.airlock.iam.login.application.service.mtan;

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
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.Validator;
import com.airlock.iam.base.api.domain.model.validation.ValidatorError;
import com.airlock.iam.common.domain.service.phone.PhoneNumberService;
import com.airlock.iam.core.misc.impl.token.MtanItem;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.shared.domain.model.mtan.MtanTokens;
import com.airlock.iam.login.application.configuration.mtan.PhoneNumberChangeSettings;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import java.util.Optional;
import java.util.regex.Pattern;
import org.apache.commons.lang3.Strings;

public class MtanNumberChangeValidator implements Validator<MtanSelfManagementService.MtanNumberChange> {
   private static final ELogger LOG = ELoggerFactory.getLogger(MtanNumberChangeValidator.class);
   private Validator<String> phoneNumberValidator;
   private PhoneNumberService phoneNumberService;
   private MtanTokens mtanTokens;
   private Optional<PhoneNumberChangeSettings> phoneNumberChangeSettings;

   @Inject
   public MtanNumberChangeValidator(
      Validator<String> phoneNumberValidator,
      PhoneNumberService phoneNumberService,
      MtanTokens mtanTokens,
      Optional<PhoneNumberChangeSettings> phoneNumberChangeSettings
   ) {
      this.phoneNumberValidator = phoneNumberValidator;
      this.phoneNumberService = phoneNumberService;
      this.mtanTokens = mtanTokens;
      this.phoneNumberChangeSettings = phoneNumberChangeSettings;
   }

   public ValidationResult validate(MtanSelfManagementService.MtanNumberChange numberChange) {
      ValidationResult result = this.phoneNumberValidator.validate(numberChange.newPhoneNumber());
      if (!result.isValid()) {
         ValidatorError phoneNumberError = result.getErrors().getFirst();
         return ValidationResult.invalid(
            ValidatorError.validatorErrorBuilder("newPhoneNumber", phoneNumberError.getType()).parameters(phoneNumberError.getParameters())
         );
      }

      String newPhoneNumber;
      try {
         newPhoneNumber = this.normalizePhoneNumber(numberChange.newPhoneNumber());
      } catch (ViolationException e) {
         return this.wrongFormatError();
      }

      if (this.phoneNumberChangeSettings.isPresent()) {
         Optional<Pattern> validationRegex = this.phoneNumberChangeSettings.get().getPhoneNumberValidationRegex();
         if (validationRegex.isPresent() && !validationRegex.get().matcher(newPhoneNumber).matches()) {
            LOG.debug("The normalized phone number does not match the configured phone number validation regex.");
            return this.wrongFormatError();
         }
      }

      Optional<MtanItem> item = this.mtanTokens.findMtanToken(numberChange.userId(), numberChange.mtanId());
      if (item.isEmpty()) {
         LOG.debug(String.format("No mTAN item with ID '%s' found", numberChange.mtanId()));
         return ValidationResult.invalid(ValidatorError.validatorError(GenericError.NOT_FOUND));
      } else {
         boolean conflictsWithExistingNumber = this.mtanTokens
            .getMtanTokens(numberChange.userId())
            .stream()
            .anyMatch(token -> this.matchesNumberOf(newPhoneNumber, token));
         if (conflictsWithExistingNumber) {
            LOG.debug("A conflict with a phone number of an existing mTAN token was found");
            return ValidationResult.invalid(ValidatorError.validatorError("newPhoneNumber", GenericError.NOT_UNIQUE));
         } else {
            return ValidationResult.valid();
         }
      }
   }

   private ValidationResult wrongFormatError() {
      return ValidationResult.invalid(ValidatorError.validatorError("newPhoneNumber", GenericError.WRONG_FORMAT));
   }

   private boolean matchesNumberOf(String newNumber, MtanItem item) {
      String existingNumber = this.normalizePhoneNumber(item.getMobilePhoneNumber());
      return Strings.CS.equals(existingNumber, this.normalizePhoneNumber(newNumber));
   }

   private String normalizePhoneNumber(String phoneNumber) {
      return this.phoneNumberService.normalizePhoneNumber(phoneNumber);
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class MtanNumberChangeValidatorBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Validator<String> phoneNumberValidator;
      @SuppressFBWarnings(justification = "generated code")
      private PhoneNumberService phoneNumberService;
      @SuppressFBWarnings(justification = "generated code")
      private MtanTokens mtanTokens;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<PhoneNumberChangeSettings> phoneNumberChangeSettings;

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

      @SuppressFBWarnings(justification = "generated code")
      public MtanNumberChangeValidator.MtanNumberChangeValidatorBuilder phoneNumberValidator(Validator<String> phoneNumberValidator) {
         this.phoneNumberValidator = phoneNumberValidator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanNumberChangeValidator.MtanNumberChangeValidatorBuilder phoneNumberService(PhoneNumberService phoneNumberService) {
         this.phoneNumberService = phoneNumberService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanNumberChangeValidator.MtanNumberChangeValidatorBuilder mtanTokens(MtanTokens mtanTokens) {
         this.mtanTokens = mtanTokens;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanNumberChangeValidator.MtanNumberChangeValidatorBuilder phoneNumberChangeSettings(Optional<PhoneNumberChangeSettings> phoneNumberChangeSettings) {
         this.phoneNumberChangeSettings = phoneNumberChangeSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanNumberChangeValidator build() {
         return new MtanNumberChangeValidator(this.phoneNumberValidator, this.phoneNumberService, this.mtanTokens, this.phoneNumberChangeSettings);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanNumberChangeValidator.MtanNumberChangeValidatorBuilder(phoneNumberValidator="
            + this.phoneNumberValidator
            + ", phoneNumberService="
            + this.phoneNumberService
            + ", mtanTokens="
            + this.mtanTokens
            + ", phoneNumberChangeSettings="
            + this.phoneNumberChangeSettings
            + ")";
      }
   }
}
