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

import com.airlock.iam.base.api.domain.model.exception.EntityNotFoundException;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.api.domain.model.user.UserId;
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.misc.data.OptionalBoolean;
import com.airlock.iam.base.misc.data.OptionalString;
import com.airlock.iam.base.misc.date.LocalDateHelper;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.otp.OneTimePassword;
import com.airlock.iam.common.api.domain.model.otp.Otp;
import com.airlock.iam.common.api.domain.model.otp.OtpEqualityStrategy;
import com.airlock.iam.common.api.domain.model.otp.UnverifiedOtp;
import com.airlock.iam.common.domain.model.auth.ExternalizationContext;
import com.airlock.iam.common.domain.model.otp.NonExpiringOneTimePassword;
import com.airlock.iam.common.domain.model.otp.OneTimePasswordFactory;
import com.airlock.iam.common.domain.model.sms.Sms;
import com.airlock.iam.common.domain.service.exception.FeatureDisabledException;
import com.airlock.iam.common.domain.service.externalization.ExternalizationService;
import com.airlock.iam.common.domain.service.phone.PhoneNumberService;
import com.airlock.iam.common.domain.service.sms.MtanSmsFactory;
import com.airlock.iam.common.domain.service.sms.SmsService;
import com.airlock.iam.core.misc.authen.result.AuthenticationFailed;
import com.airlock.iam.core.misc.impl.token.MtanItem;
import com.airlock.iam.flow.shared.api.domain.model.message.MtanMessageProvider;
import com.airlock.iam.flow.shared.domain.model.mtan.MtanTokens;
import com.airlock.iam.log.api.signing.SigningLogger;
import com.airlock.iam.login.application.configuration.mtan.PhoneNumberChangeSettings;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import lombok.NonNull;

public class MtanSelfManagementService {
   static final String OTP_KEY = "otp";
   static final String MTAN_ID_KEY = "mtan-id";
   static final String NEW_PHONE_NUMBER_KEY = "new-phone-number";
   static final String OLD_PHONE_NUMBER_KEY = "old-phone-number";
   private final MtanSelfManagementService.MtanTokenManagementSettings settings;
   private final SigningLogger signingLogger;
   private final ExternalizationService externalizationService;
   private final Provider<MtanMessageProvider> messageProvider;

   @Inject
   public MtanSelfManagementService(
      MtanSelfManagementService.MtanTokenManagementSettings settings,
      SigningLogger signingLogger,
      ExternalizationService externalizationService,
      Provider<MtanMessageProvider> messageProvider
   ) {
      this.settings = settings;
      this.signingLogger = signingLogger;
      this.externalizationService = externalizationService;
      this.messageProvider = messageProvider;
   }

   public List<AuthenticationFailed.MtanChoiceToken> getMtanTokens(UserId userId) {
      return this.settings.mtanTokens().createChoicesForManagement(userId);
   }

   public AuthenticationFailed.MtanChoiceToken updateMtanTokenData(MtanSelfManagementService.MtanItemUpdate item) {
      MtanItem existing = this.failUnlessItemExists(item.getUserId(), item.getMtanTokenId());
      existing.setLabel(item.getLabel().orElse(existing.getLabel()));
      existing.setSendSmsAsFlash(item.getFlash().orElse(existing.getSendSmsAsFlash()));
      this.settings.mtanTokens().store(item.getUserId(), existing);
      return this.settings.mtanTokens().findMtanTokenChoice(item.getUserId(), item.getMtanTokenId()).orElseThrow();
   }

   public ValidationResult validate(MtanSelfManagementService.MtanNumberChange change) {
      FeatureDisabledException.checkOptionalPresent(this.settings.phoneNumberChangeSettings(), "Phone number change is disabled.");
      return this.settings.numberChangeValidator().validate(change);
   }

   public ExternalizationContext startPhoneNumberChange(MtanSelfManagementService.MtanNumberChange change) {
      PhoneNumberChangeSettings changeSettings = FeatureDisabledException.checkOptionalPresent(
         this.settings.phoneNumberChangeSettings(), "Phone number change is disabled."
      );
      ViolationException.checkTrue(this.validate(change).isValid(), "The phone number to change to is invalid");
      String newNumber = this.normalizePhoneNumber(change.newPhoneNumber());
      OneTimePassword otp = this.settings.otpFactory().createNonExpiringOneTimePassword();
      this.sendOtp(newNumber, otp.value());
      MtanTokens tokens = this.settings.mtanTokens();
      MtanItem mtanItem = tokens.getMtanToken(change.userId(), change.mtanId());
      ExternalizationContext.ExternalizationContextContent content = ExternalizationContext.ExternalizationContextContent.builder()
         .userId(change.userId().getValue())
         .expirationDate(LocalDateHelper.toDate(LocalDateTime.now().plus(changeSettings.getValidityDuration())))
         .value("otp", otp.value().rawValue())
         .value("new-phone-number", newNumber)
         .value("old-phone-number", mtanItem.getMobilePhoneNumber())
         .value("mtan-id", change.mtanId())
         .build();
      this.signingLogger
         .log("Started phone number update for mTAN item %s of user '%s'", new Object[]{this.toMaskedNumber(change.userId(), change.mtanId()), change.userId()});
      return this.externalizationService.create(content).orElseThrow();
   }

   public boolean isValid(MtanSelfManagementService.MtanNumberChangeVerification verification) {
      Optional<ExternalizationContext.ExternalizationContextContent> content = this.externalizationService.verify(verification.context());
      return this.isValid(verification, content);
   }

   public void applyPhoneNumberChange(MtanSelfManagementService.MtanNumberChangeVerification verification) {
      FeatureDisabledException.checkOptionalPresent(this.settings.phoneNumberChangeSettings(), "Phone number change is disabled.");
      Optional<ExternalizationContext.ExternalizationContextContent> content = this.externalizationService.verify(verification.context());
      if (!this.isValid(verification, content)) {
         throw new ViolationException("The verification for the mTAN phone number change has failed.");
      }

      String newPhoneNumber = content.orElseThrow().getValues().get("new-phone-number");
      MtanItem existing = this.failUnlessItemExists(verification.userId(), verification.mtanId());
      existing.setMobilePhoneNumber(this.normalizePhoneNumber(newPhoneNumber));
      this.settings.mtanTokens().store(verification.userId(), existing);
      this.signingLogger
         .log(
            "Changed phone number for user '%s' to '%s'",
            new Object[]{verification.userId(), this.toMaskedNumber(verification.userId(), verification.mtanId())}
         );
   }

   private boolean isValid(
      MtanSelfManagementService.MtanNumberChangeVerification verification, Optional<ExternalizationContext.ExternalizationContextContent> contextContent
   ) {
      if (contextContent.isEmpty()) {
         this.signingLogger.log("Failed to verify the mTAN phone number change context for user '%s'", new Object[]{verification.userId()});
         return false;
      } else {
         ExternalizationContext.ExternalizationContextContent content = contextContent.get();
         String mtanId = content.getValues().get("mtan-id");
         if (!mtanId.equals(verification.mtanId())) {
            this.signingLogger
               .log(
                  "The verification context does not match the mTAN item ID from the request '%s'",
                  new Object[]{this.toMaskedNumber(verification.userId(), verification.mtanId())}
               );
            return false;
         } else {
            NonExpiringOneTimePassword otp = NonExpiringOneTimePassword.nonExpiringOneTimeCode(Otp.otp(content.getValues().get("otp")));
            if (!this.verifyOtp(otp, verification.inputOtp)) {
               this.signingLogger.log("The OTP for the mTAN number change for user '%s' was incorrect", new Object[]{verification.userId()});
               return false;
            } else {
               MtanItem mtanItem = this.settings.mtanTokens().getMtanToken(verification.userId(), verification.mtanId());
               String oldPhoneNumber = content.getValues().get("old-phone-number");
               if (!oldPhoneNumber.equals(mtanItem.getMobilePhoneNumber())) {
                  this.signingLogger
                     .log("The mTAN item's phone number does not match the old number in the context for user '%s'", new Object[]{verification.userId()});
                  return false;
               } else {
                  return true;
               }
            }
         }
      }
   }

   private boolean verifyOtp(NonExpiringOneTimePassword otp, UnverifiedOtp inputOtp) {
      return switch (otp.verify(inputOtp, OtpEqualityStrategy.equalityStrategy(!this.settings.otpCaseSensitive()))) {
         case EXPIRED -> throw new FailureException("Unexpected result for a non-expiring one-time code");
         case WRONG_VALUE -> false;
         case SUCCESS -> true;
      };
   }

   private String toMaskedNumber(UserId userId, String id) {
      Optional<MtanItem> number = this.settings.mtanTokens().findMtanToken(userId, id);
      return number.isPresent() ? this.settings.phoneNumberService.maskPhoneNumber(number.get().getMobilePhoneNumber()) : "(mTAN item " + id + ")";
   }

   private void sendOtp(String phoneNumber, Otp otp) {
      String message = ((MtanMessageProvider)this.messageProvider.get()).generateMtanMessage();
      Sms sms = this.settings.smsFactory().createSmsFor(otp, phoneNumber, message);
      this.settings.smsService().send(sms);
   }

   private MtanItem failUnlessItemExists(UserId userId, String mtanId) {
      Optional<MtanItem> existingItem = this.settings.mtanTokens().findMtanToken(userId, mtanId);
      return existingItem.orElseThrow(
         () -> new EntityNotFoundException(String.format("No mTAN item with ID '%s' found for user '%s'", LogUtil.logSafeString(mtanId), userId.getValue()))
      );
   }

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

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

   public static final class MtanItemUpdate {
      private UserId userId;
      private String mtanTokenId;
      private final OptionalString label;
      private final OptionalBoolean flash;

      public static MtanSelfManagementService.MtanItemUpdate.MtanItemUpdateBuilder builder() {
         return new MtanSelfManagementService.MtanItemUpdate().toBuilder();
      }

      private MtanItemUpdate() {
         this.label = OptionalString.none();
         this.flash = OptionalBoolean.none();
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanSelfManagementService.MtanItemUpdate.MtanItemUpdateBuilder toBuilder() {
         return new MtanSelfManagementService.MtanItemUpdate.MtanItemUpdateBuilder()
            .userId(this.userId)
            .mtanTokenId(this.mtanTokenId)
            .label(this.label)
            .flash(this.flash);
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserId getUserId() {
         return this.userId;
      }

      @SuppressFBWarnings(justification = "generated code")
      public String getMtanTokenId() {
         return this.mtanTokenId;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OptionalString getLabel() {
         return this.label;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OptionalBoolean getFlash() {
         return this.flash;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanItemUpdate(UserId userId, String mtanTokenId, OptionalString label, OptionalBoolean flash) {
         this.userId = userId;
         this.mtanTokenId = mtanTokenId;
         this.label = label;
         this.flash = flash;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class MtanItemUpdateBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private UserId userId;
         @SuppressFBWarnings(justification = "generated code")
         private String mtanTokenId;
         @SuppressFBWarnings(justification = "generated code")
         private OptionalString label;
         @SuppressFBWarnings(justification = "generated code")
         private OptionalBoolean flash;

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanItemUpdate.MtanItemUpdateBuilder userId(UserId userId) {
            this.userId = userId;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanItemUpdate.MtanItemUpdateBuilder mtanTokenId(String mtanTokenId) {
            this.mtanTokenId = mtanTokenId;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanItemUpdate.MtanItemUpdateBuilder label(OptionalString label) {
            this.label = label;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanItemUpdate.MtanItemUpdateBuilder flash(OptionalBoolean flash) {
            this.flash = flash;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanItemUpdate build() {
            return new MtanSelfManagementService.MtanItemUpdate(this.userId, this.mtanTokenId, this.label, this.flash);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MtanSelfManagementService.MtanItemUpdate.MtanItemUpdateBuilder(userId="
               + this.userId
               + ", mtanTokenId="
               + this.mtanTokenId
               + ", label="
               + this.label
               + ", flash="
               + this.flash
               + ")";
         }
      }
   }

   public static final class MtanNumberChange {
      @NonNull
      private final UserId userId;
      @NonNull
      private final String mtanId;
      @NonNull
      private final String newPhoneNumber;

      @SuppressFBWarnings(justification = "generated code")
      MtanNumberChange(@NonNull UserId userId, @NonNull String mtanId, @NonNull String newPhoneNumber) {
         if (userId == null) {
            throw new NullPointerException("userId is marked non-null but is null");
         }

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

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

         this.userId = userId;
         this.mtanId = mtanId;
         this.newPhoneNumber = newPhoneNumber;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static MtanSelfManagementService.MtanNumberChange.MtanNumberChangeBuilder builder() {
         return new MtanSelfManagementService.MtanNumberChange.MtanNumberChangeBuilder();
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public UserId userId() {
         return this.userId;
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public String mtanId() {
         return this.mtanId;
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public String newPhoneNumber() {
         return this.newPhoneNumber;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof MtanSelfManagementService.MtanNumberChange other)) {
            return false;
         } else {
            Object this$userId = this.userId;
            Object other$userId = other.userId;
            if (this$userId == null ? other$userId == null : this$userId.equals(other$userId)) {
               Object this$mtanId = this.mtanId;
               Object other$mtanId = other.mtanId;
               if (this$mtanId == null ? other$mtanId == null : this$mtanId.equals(other$mtanId)) {
                  Object this$newPhoneNumber = this.newPhoneNumber;
                  Object other$newPhoneNumber = other.newPhoneNumber;
                  return this$newPhoneNumber == null ? other$newPhoneNumber == null : this$newPhoneNumber.equals(other$newPhoneNumber);
               } else {
                  return false;
               }
            } else {
               return false;
            }
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         Object $userId = this.userId;
         result = result * 59 + ($userId == null ? 43 : $userId.hashCode());
         Object $mtanId = this.mtanId;
         result = result * 59 + ($mtanId == null ? 43 : $mtanId.hashCode());
         Object $newPhoneNumber = this.newPhoneNumber;
         return result * 59 + ($newPhoneNumber == null ? 43 : $newPhoneNumber.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanSelfManagementService.MtanNumberChange(userId="
            + this.userId()
            + ", mtanId="
            + this.mtanId()
            + ", newPhoneNumber="
            + this.newPhoneNumber()
            + ")";
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class MtanNumberChangeBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private UserId userId;
         @SuppressFBWarnings(justification = "generated code")
         private String mtanId;
         @SuppressFBWarnings(justification = "generated code")
         private String newPhoneNumber;

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanNumberChange.MtanNumberChangeBuilder userId(@NonNull UserId userId) {
            if (userId == null) {
               throw new NullPointerException("userId is marked non-null but is null");
            }

            this.userId = userId;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanNumberChange.MtanNumberChangeBuilder mtanId(@NonNull String mtanId) {
            if (mtanId == null) {
               throw new NullPointerException("mtanId is marked non-null but is null");
            }

            this.mtanId = mtanId;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanNumberChange.MtanNumberChangeBuilder newPhoneNumber(@NonNull String newPhoneNumber) {
            if (newPhoneNumber == null) {
               throw new NullPointerException("newPhoneNumber is marked non-null but is null");
            }

            this.newPhoneNumber = newPhoneNumber;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanNumberChange build() {
            return new MtanSelfManagementService.MtanNumberChange(this.userId, this.mtanId, this.newPhoneNumber);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MtanSelfManagementService.MtanNumberChange.MtanNumberChangeBuilder(userId="
               + this.userId
               + ", mtanId="
               + this.mtanId
               + ", newPhoneNumber="
               + this.newPhoneNumber
               + ")";
         }
      }
   }

   public static final class MtanNumberChangeVerification {
      @NonNull
      private final UserId userId;
      @NonNull
      private final String mtanId;
      @NonNull
      private final UnverifiedOtp inputOtp;
      @NonNull
      private final ExternalizationContext context;

      @SuppressFBWarnings(justification = "generated code")
      MtanNumberChangeVerification(@NonNull UserId userId, @NonNull String mtanId, @NonNull UnverifiedOtp inputOtp, @NonNull ExternalizationContext context) {
         if (userId == null) {
            throw new NullPointerException("userId is marked non-null but is null");
         }

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

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

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

         this.userId = userId;
         this.mtanId = mtanId;
         this.inputOtp = inputOtp;
         this.context = context;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static MtanSelfManagementService.MtanNumberChangeVerification.MtanNumberChangeVerificationBuilder builder() {
         return new MtanSelfManagementService.MtanNumberChangeVerification.MtanNumberChangeVerificationBuilder();
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public UserId userId() {
         return this.userId;
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public String mtanId() {
         return this.mtanId;
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public UnverifiedOtp inputOtp() {
         return this.inputOtp;
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public ExternalizationContext context() {
         return this.context;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof MtanSelfManagementService.MtanNumberChangeVerification other)) {
            return false;
         } else {
            Object this$userId = this.userId;
            Object other$userId = other.userId;
            if (this$userId == null ? other$userId == null : this$userId.equals(other$userId)) {
               Object this$mtanId = this.mtanId;
               Object other$mtanId = other.mtanId;
               if (this$mtanId == null ? other$mtanId == null : this$mtanId.equals(other$mtanId)) {
                  Object this$inputOtp = this.inputOtp;
                  Object other$inputOtp = other.inputOtp;
                  if (this$inputOtp == null ? other$inputOtp == null : this$inputOtp.equals(other$inputOtp)) {
                     Object this$context = this.context;
                     Object other$context = other.context;
                     return this$context == null ? other$context == null : this$context.equals(other$context);
                  } else {
                     return false;
                  }
               } else {
                  return false;
               }
            } else {
               return false;
            }
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         Object $userId = this.userId;
         result = result * 59 + ($userId == null ? 43 : $userId.hashCode());
         Object $mtanId = this.mtanId;
         result = result * 59 + ($mtanId == null ? 43 : $mtanId.hashCode());
         Object $inputOtp = this.inputOtp;
         result = result * 59 + ($inputOtp == null ? 43 : $inputOtp.hashCode());
         Object $context = this.context;
         return result * 59 + ($context == null ? 43 : $context.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanSelfManagementService.MtanNumberChangeVerification(userId="
            + this.userId()
            + ", mtanId="
            + this.mtanId()
            + ", inputOtp="
            + this.inputOtp()
            + ", context="
            + this.context()
            + ")";
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class MtanNumberChangeVerificationBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private UserId userId;
         @SuppressFBWarnings(justification = "generated code")
         private String mtanId;
         @SuppressFBWarnings(justification = "generated code")
         private UnverifiedOtp inputOtp;
         @SuppressFBWarnings(justification = "generated code")
         private ExternalizationContext context;

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanNumberChangeVerification.MtanNumberChangeVerificationBuilder userId(@NonNull UserId userId) {
            if (userId == null) {
               throw new NullPointerException("userId is marked non-null but is null");
            }

            this.userId = userId;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanNumberChangeVerification.MtanNumberChangeVerificationBuilder mtanId(@NonNull String mtanId) {
            if (mtanId == null) {
               throw new NullPointerException("mtanId is marked non-null but is null");
            }

            this.mtanId = mtanId;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanNumberChangeVerification.MtanNumberChangeVerificationBuilder inputOtp(@NonNull UnverifiedOtp inputOtp) {
            if (inputOtp == null) {
               throw new NullPointerException("inputOtp is marked non-null but is null");
            }

            this.inputOtp = inputOtp;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanNumberChangeVerification.MtanNumberChangeVerificationBuilder context(@NonNull ExternalizationContext context) {
            if (context == null) {
               throw new NullPointerException("context is marked non-null but is null");
            }

            this.context = context;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanNumberChangeVerification build() {
            return new MtanSelfManagementService.MtanNumberChangeVerification(this.userId, this.mtanId, this.inputOtp, this.context);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MtanSelfManagementService.MtanNumberChangeVerification.MtanNumberChangeVerificationBuilder(userId="
               + this.userId
               + ", mtanId="
               + this.mtanId
               + ", inputOtp="
               + this.inputOtp
               + ", context="
               + this.context
               + ")";
         }
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class MtanSelfManagementServiceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private MtanSelfManagementService.MtanTokenManagementSettings settings;
      @SuppressFBWarnings(justification = "generated code")
      private SigningLogger signingLogger;
      @SuppressFBWarnings(justification = "generated code")
      private ExternalizationService externalizationService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<MtanMessageProvider> messageProvider;

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

      @SuppressFBWarnings(justification = "generated code")
      public MtanSelfManagementService.MtanSelfManagementServiceBuilder settings(MtanSelfManagementService.MtanTokenManagementSettings settings) {
         this.settings = settings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanSelfManagementService.MtanSelfManagementServiceBuilder signingLogger(SigningLogger signingLogger) {
         this.signingLogger = signingLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanSelfManagementService.MtanSelfManagementServiceBuilder externalizationService(ExternalizationService externalizationService) {
         this.externalizationService = externalizationService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanSelfManagementService.MtanSelfManagementServiceBuilder messageProvider(Provider<MtanMessageProvider> messageProvider) {
         this.messageProvider = messageProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanSelfManagementService build() {
         return new MtanSelfManagementService(this.settings, this.signingLogger, this.externalizationService, this.messageProvider);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanSelfManagementService.MtanSelfManagementServiceBuilder(settings="
            + this.settings
            + ", signingLogger="
            + this.signingLogger
            + ", externalizationService="
            + this.externalizationService
            + ", messageProvider="
            + this.messageProvider
            + ")";
      }
   }

   public static final class MtanTokenManagementSettings {
      @NonNull
      private final MtanTokens mtanTokens;
      @NonNull
      private final PhoneNumberService phoneNumberService;
      @NonNull
      private final OneTimePasswordFactory otpFactory;
      @NonNull
      private final Boolean otpCaseSensitive;
      @NonNull
      private final SmsService smsService;
      @NonNull
      private final MtanSmsFactory smsFactory;
      @NonNull
      private final Validator<MtanSelfManagementService.MtanNumberChange> numberChangeValidator;
      @NonNull
      private final Optional<PhoneNumberChangeSettings> phoneNumberChangeSettings;

      @SuppressFBWarnings(justification = "generated code")
      MtanTokenManagementSettings(
         @NonNull MtanTokens mtanTokens,
         @NonNull PhoneNumberService phoneNumberService,
         @NonNull OneTimePasswordFactory otpFactory,
         @NonNull Boolean otpCaseSensitive,
         @NonNull SmsService smsService,
         @NonNull MtanSmsFactory smsFactory,
         @NonNull Validator<MtanSelfManagementService.MtanNumberChange> numberChangeValidator,
         @NonNull Optional<PhoneNumberChangeSettings> phoneNumberChangeSettings
      ) {
         if (mtanTokens == null) {
            throw new NullPointerException("mtanTokens is marked non-null but is null");
         }

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

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

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

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

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

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

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

         this.mtanTokens = mtanTokens;
         this.phoneNumberService = phoneNumberService;
         this.otpFactory = otpFactory;
         this.otpCaseSensitive = otpCaseSensitive;
         this.smsService = smsService;
         this.smsFactory = smsFactory;
         this.numberChangeValidator = numberChangeValidator;
         this.phoneNumberChangeSettings = phoneNumberChangeSettings;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder builder() {
         return new MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder();
      }

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

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

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public OneTimePasswordFactory otpFactory() {
         return this.otpFactory;
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public Boolean otpCaseSensitive() {
         return this.otpCaseSensitive;
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public SmsService smsService() {
         return this.smsService;
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public MtanSmsFactory smsFactory() {
         return this.smsFactory;
      }

      @NonNull
      @SuppressFBWarnings(justification = "generated code")
      public Validator<MtanSelfManagementService.MtanNumberChange> numberChangeValidator() {
         return this.numberChangeValidator;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof MtanSelfManagementService.MtanTokenManagementSettings other)) {
            return false;
         } else {
            Object this$otpCaseSensitive = this.otpCaseSensitive;
            Object other$otpCaseSensitive = other.otpCaseSensitive;
            if (this$otpCaseSensitive == null ? other$otpCaseSensitive == null : this$otpCaseSensitive.equals(other$otpCaseSensitive)) {
               Object this$mtanTokens = this.mtanTokens;
               Object other$mtanTokens = other.mtanTokens;
               if (this$mtanTokens == null ? other$mtanTokens == null : this$mtanTokens.equals(other$mtanTokens)) {
                  Object this$phoneNumberService = this.phoneNumberService;
                  Object other$phoneNumberService = other.phoneNumberService;
                  if (this$phoneNumberService == null ? other$phoneNumberService == null : this$phoneNumberService.equals(other$phoneNumberService)) {
                     Object this$otpFactory = this.otpFactory;
                     Object other$otpFactory = other.otpFactory;
                     if (this$otpFactory == null ? other$otpFactory == null : this$otpFactory.equals(other$otpFactory)) {
                        Object this$smsService = this.smsService;
                        Object other$smsService = other.smsService;
                        if (this$smsService == null ? other$smsService == null : this$smsService.equals(other$smsService)) {
                           Object this$smsFactory = this.smsFactory;
                           Object other$smsFactory = other.smsFactory;
                           if (this$smsFactory == null ? other$smsFactory == null : this$smsFactory.equals(other$smsFactory)) {
                              Object this$numberChangeValidator = this.numberChangeValidator;
                              Object other$numberChangeValidator = other.numberChangeValidator;
                              if (this$numberChangeValidator == null
                                 ? other$numberChangeValidator == null
                                 : this$numberChangeValidator.equals(other$numberChangeValidator)) {
                                 Object this$phoneNumberChangeSettings = this.phoneNumberChangeSettings;
                                 Object other$phoneNumberChangeSettings = other.phoneNumberChangeSettings;
                                 return this$phoneNumberChangeSettings == null
                                    ? other$phoneNumberChangeSettings == null
                                    : this$phoneNumberChangeSettings.equals(other$phoneNumberChangeSettings);
                              } else {
                                 return false;
                              }
                           } else {
                              return false;
                           }
                        } else {
                           return false;
                        }
                     } 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;
         Object $otpCaseSensitive = this.otpCaseSensitive;
         result = result * 59 + ($otpCaseSensitive == null ? 43 : $otpCaseSensitive.hashCode());
         Object $mtanTokens = this.mtanTokens;
         result = result * 59 + ($mtanTokens == null ? 43 : $mtanTokens.hashCode());
         Object $phoneNumberService = this.phoneNumberService;
         result = result * 59 + ($phoneNumberService == null ? 43 : $phoneNumberService.hashCode());
         Object $otpFactory = this.otpFactory;
         result = result * 59 + ($otpFactory == null ? 43 : $otpFactory.hashCode());
         Object $smsService = this.smsService;
         result = result * 59 + ($smsService == null ? 43 : $smsService.hashCode());
         Object $smsFactory = this.smsFactory;
         result = result * 59 + ($smsFactory == null ? 43 : $smsFactory.hashCode());
         Object $numberChangeValidator = this.numberChangeValidator;
         result = result * 59 + ($numberChangeValidator == null ? 43 : $numberChangeValidator.hashCode());
         Object $phoneNumberChangeSettings = this.phoneNumberChangeSettings;
         return result * 59 + ($phoneNumberChangeSettings == null ? 43 : $phoneNumberChangeSettings.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanSelfManagementService.MtanTokenManagementSettings(mtanTokens="
            + this.mtanTokens()
            + ", phoneNumberService="
            + this.phoneNumberService()
            + ", otpFactory="
            + this.otpFactory()
            + ", otpCaseSensitive="
            + this.otpCaseSensitive()
            + ", smsService="
            + this.smsService()
            + ", smsFactory="
            + this.smsFactory()
            + ", numberChangeValidator="
            + this.numberChangeValidator()
            + ", phoneNumberChangeSettings="
            + this.phoneNumberChangeSettings()
            + ")";
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class MtanTokenManagementSettingsBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private MtanTokens mtanTokens;
         @SuppressFBWarnings(justification = "generated code")
         private PhoneNumberService phoneNumberService;
         @SuppressFBWarnings(justification = "generated code")
         private OneTimePasswordFactory otpFactory;
         @SuppressFBWarnings(justification = "generated code")
         private Boolean otpCaseSensitive;
         @SuppressFBWarnings(justification = "generated code")
         private SmsService smsService;
         @SuppressFBWarnings(justification = "generated code")
         private MtanSmsFactory smsFactory;
         @SuppressFBWarnings(justification = "generated code")
         private Validator<MtanSelfManagementService.MtanNumberChange> numberChangeValidator;
         @SuppressFBWarnings(justification = "generated code")
         private Optional<PhoneNumberChangeSettings> phoneNumberChangeSettings;

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder mtanTokens(@NonNull MtanTokens mtanTokens) {
            if (mtanTokens == null) {
               throw new NullPointerException("mtanTokens is marked non-null but is null");
            }

            this.mtanTokens = mtanTokens;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder phoneNumberService(
            @NonNull PhoneNumberService phoneNumberService
         ) {
            if (phoneNumberService == null) {
               throw new NullPointerException("phoneNumberService is marked non-null but is null");
            }

            this.phoneNumberService = phoneNumberService;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder otpFactory(@NonNull OneTimePasswordFactory otpFactory) {
            if (otpFactory == null) {
               throw new NullPointerException("otpFactory is marked non-null but is null");
            }

            this.otpFactory = otpFactory;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder otpCaseSensitive(@NonNull Boolean otpCaseSensitive) {
            if (otpCaseSensitive == null) {
               throw new NullPointerException("otpCaseSensitive is marked non-null but is null");
            }

            this.otpCaseSensitive = otpCaseSensitive;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder smsService(@NonNull SmsService smsService) {
            if (smsService == null) {
               throw new NullPointerException("smsService is marked non-null but is null");
            }

            this.smsService = smsService;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder smsFactory(@NonNull MtanSmsFactory smsFactory) {
            if (smsFactory == null) {
               throw new NullPointerException("smsFactory is marked non-null but is null");
            }

            this.smsFactory = smsFactory;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder numberChangeValidator(
            @NonNull Validator<MtanSelfManagementService.MtanNumberChange> numberChangeValidator
         ) {
            if (numberChangeValidator == null) {
               throw new NullPointerException("numberChangeValidator is marked non-null but is null");
            }

            this.numberChangeValidator = numberChangeValidator;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder phoneNumberChangeSettings(
            @NonNull Optional<PhoneNumberChangeSettings> phoneNumberChangeSettings
         ) {
            if (phoneNumberChangeSettings == null) {
               throw new NullPointerException("phoneNumberChangeSettings is marked non-null but is null");
            }

            this.phoneNumberChangeSettings = phoneNumberChangeSettings;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanSelfManagementService.MtanTokenManagementSettings build() {
            return new MtanSelfManagementService.MtanTokenManagementSettings(
               this.mtanTokens,
               this.phoneNumberService,
               this.otpFactory,
               this.otpCaseSensitive,
               this.smsService,
               this.smsFactory,
               this.numberChangeValidator,
               this.phoneNumberChangeSettings
            );
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MtanSelfManagementService.MtanTokenManagementSettings.MtanTokenManagementSettingsBuilder(mtanTokens="
               + this.mtanTokens
               + ", phoneNumberService="
               + this.phoneNumberService
               + ", otpFactory="
               + this.otpFactory
               + ", otpCaseSensitive="
               + this.otpCaseSensitive
               + ", smsService="
               + this.smsService
               + ", smsFactory="
               + this.smsFactory
               + ", numberChangeValidator="
               + this.numberChangeValidator
               + ", phoneNumberChangeSettings="
               + this.phoneNumberChangeSettings
               + ")";
         }
      }
   }
}
