package com.airlock.iam.admin.application.service.token.mtan;

import com.airlock.iam.admin.api.domain.model.administrator.CurrentAdministrator;
import com.airlock.iam.admin.application.configuration.mtan.MtanTokenController;
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.GenericError;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.misc.data.OptionalBoolean;
import com.airlock.iam.base.misc.data.OptionalString;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.mtan.MtanNumberId;
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.phonenumber.PhoneNumber;
import com.airlock.iam.common.api.domain.model.token.exception.TokenCountLimitException;
import com.airlock.iam.common.application.service.token.mtan.MtanTokenOrderService;
import com.airlock.iam.common.domain.model.mtan.Iak;
import com.airlock.iam.common.domain.model.mtan.MtanItemBackedIak;
import com.airlock.iam.common.domain.model.mtan.MtanItemView;
import com.airlock.iam.common.domain.model.mtan.event.MtanTokenEventFactory;
import com.airlock.iam.common.domain.model.otp.OneTimePasswordVerificationContext;
import com.airlock.iam.common.domain.model.sms.Sms;
import com.airlock.iam.common.domain.model.sms.Sms.SmsBuilder;
import com.airlock.iam.common.domain.model.token.mtan.CommonMtanTokenRepository;
import com.airlock.iam.common.domain.model.token.mtan.MtanTokenRepository;
import com.airlock.iam.common.domain.service.exception.FeatureDisabledException;
import com.airlock.iam.common.domain.service.phone.PhoneNumberService;
import com.airlock.iam.common.domain.service.sms.SmsService;
import com.airlock.iam.core.misc.impl.authen.mtan.MtanHandler;
import com.airlock.iam.core.misc.impl.authen.mtan.MtanHandlerException;
import com.airlock.iam.core.misc.impl.authen.mtan.MtanSettings;
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.log.api.signing.SigningLogger;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Provider;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;

public class MtanTokenService {
   private static final ELogger LOG = ELoggerFactory.getLogger(MtanTokenService.class);
   private static final String TOKEN_VARIABLE = "$TOKEN$";
   public static final String MTAN_SMS_SERVICE_NAME = "MtanSmsService";
   private final MtanSettings mtanSettings;
   private final SmsService smsService;
   private final OneTimePasswordService oneTimePasswordService;
   private final PhoneNumberService phoneNumberService;
   private final MtanTokenController tokenController;
   private final CurrentAdministrator administrator;
   private final SigningLogger signingLogger;
   private final MtanTokenRepository repository;
   private final MtanTokenOrderService mtanTokenOrderService;
   private final Provider<MtanTokenEventFactory> eventFactory;

   @Inject
   public MtanTokenService(
      MtanSettings mtanSettings,
      @Named("MtanSmsService") SmsService smsService,
      OneTimePasswordService oneTimePasswordService,
      PhoneNumberService phoneNumberService,
      MtanTokenController tokenController,
      CurrentAdministrator administrator,
      SigningLogger signingLogger,
      MtanTokenRepository repository,
      Provider<MtanTokenEventFactory> eventFactory
   ) {
      this.mtanSettings = mtanSettings;
      this.smsService = smsService;
      this.oneTimePasswordService = oneTimePasswordService;
      this.phoneNumberService = phoneNumberService;
      this.tokenController = tokenController;
      this.administrator = administrator;
      this.signingLogger = signingLogger;
      this.eventFactory = eventFactory;
      this.repository = Objects.requireNonNullElseGet(
         repository,
         () -> new MtanTokenRepository(
            administrator.getName(),
            mtanSettings.getMtanHandler(),
            tokenController.getSecretLetterRenderer(),
            tokenController.getDeletePropertiesWithCredential(),
            new CommonMtanTokenRepository(mtanSettings.getMtanHandler())
         )
      );
      this.mtanTokenOrderService = new MtanTokenOrderService(this.signingLogger, this.repository);
   }

   public Collection<MtanItemView> retrieveMtanItems(UserId userId) {
      return this.repository.getMtanTokens(userId).stream().map(this::createViewOf).collect(Collectors.toList());
   }

   public Optional<MtanItemView> retrieveMtanItem(UserId userId, String mtanNumberId) {
      return this.createOptionalViewOf(this.repository.getMtanToken(userId, mtanNumberId));
   }

   public MtanTokenService.AlteredPhoneNumberResult addMtanItem(MtanTokenService.MtanItemParams params) throws TokenCountLimitException, ViolationException {
      Preconditions.checkNotNull(params);

      try {
         if (!this.repository.canAddMtanToken(params.getUserId())) {
            this.signingLogger.log("Could not add mTAN item because token count limit is reached for user \"%s\"", new Object[]{params.getUserId()});
            throw new TokenCountLimitException();
         }

         MtanItem mtanItem = this.mtanHandler().createMtanItem(params.getUserId().getValue(), this.administrator.getName());
         if (params.getFlash().hasValue() && mtanItem.isSendSmsAsFlashConfigurable()) {
            mtanItem.setSendSmsAsFlash((Boolean)params.getFlash().value());
         }

         if (params.getLabel().hasValue() && this.mtanHandler().supportsLabel()) {
            mtanItem.setLabel((String)params.getLabel().value());
         }

         if (params.getPhoneNumber().hasValue() && !StringUtils.isEmpty((CharSequence)params.getPhoneNumber().value())) {
            return this.registerPhoneNumber(mtanItem, (String)params.getPhoneNumber().value(), params.getUserId());
         }

         this.signingLogger.log("Added mTAN item to user \"%s\"", new Object[]{params.getUserId()});
         this.repository.store(params.getUserId(), mtanItem);
         return MtanTokenService.AlteredPhoneNumberResult.noNumberChange(
            this.createViewOf((MtanItem)this.repository.getMtanToken(params.getUserId(), mtanItem.getId()).orElseThrow()),
            MtanTokenService.MtanDataChanges.noChange()
         );
      } catch (MtanHandlerException e) {
         this.signingLogger.log("Service failure while adding an mTAN item for user \"%s\"", new Object[]{params.getUserId()});
         throw new FailureException("Error while adding MtanItem", e);
      }
   }

   private MtanTokenService.AlteredPhoneNumberResult registerPhoneNumber(MtanItem mtanItem, String newNumber, UserId userId) {
      String normalizedNumber = this.normalizePhoneNumber(newNumber);
      mtanItem.setMobilePhoneNumber(normalizedNumber);
      this.repository.store(userId, mtanItem);
      String logMessage = this.getRegisterNumberLogMessage(normalizedNumber);
      this.signingLogger.log(logMessage + " for user " + LogUtil.quote(userId) + ".");
      this.publishMtanTokenRegistered(mtanItem, userId);
      Optional<OneTimePassword> oneTimePassword = this.sendOtpToNewNumberIfConfigured(normalizedNumber);
      Optional<OneTimePasswordVerificationContext> otpVerification = oneTimePassword.<Otp>map(OneTimePassword::value)
         .map(this.oneTimePasswordService::createVerificationContext);
      return MtanTokenService.AlteredPhoneNumberResult.builder()
         .oneTimePassword(otpVerification)
         .mtanItem(this.createViewOf((MtanItem)this.repository.getMtanToken(userId, mtanItem.getId()).orElseThrow()))
         .logMessage(Optional.of(logMessage))
         .build();
   }

   private void publishMtanTokenRegistered(MtanItem mtanItem, UserId userId) {
      PhoneNumber normalizedNewPhoneNumber = PhoneNumber.phoneNumber(this.normalizePhoneNumber(mtanItem.getMobilePhoneNumber()));
      ((MtanTokenEventFactory)this.eventFactory.get()).publishMtanTokenRegisteredEvent(userId, mtanItem.getMtanNumberId(), normalizedNewPhoneNumber);
   }

   public Optional<MtanTokenService.AlteredPhoneNumberResult> updateMtanItem(MtanTokenService.MtanItemParams params) {
      Preconditions.checkNotNull(params);
      Optional<MtanItem> storedItem = this.repository.getMtanToken(params.getUserId(), params.getMtanNumberId());
      if (storedItem.isEmpty()) {
         return Optional.empty();
      }

      MtanItem item = storedItem.get();
      MtanTokenService.MtanDataChanges mtanDataChanges = new MtanTokenService.MtanDataChanges();
      if (params.getFlash().hasValue() && item.isSendSmsAsFlashConfigurable() && !Objects.equals(params.getFlash().value(), item.getSendSmsAsFlash())) {
         item.setSendSmsAsFlash((Boolean)params.getFlash().value());
         mtanDataChanges.setFlashUpdated(true);
      }

      if (params.getLabel().hasValue() && this.mtanHandler().supportsLabel() && !Strings.CS.equals((String)params.getLabel().value(), item.getLabel())) {
         item.setLabel((String)params.getLabel().value());
         mtanDataChanges.setLabelUpdated(true);
      }

      if (mtanDataChanges.hasChanges()) {
         this.signingLogger.log("Updated mTAN item with ID \"%s\" for user \"%s\"", new Object[]{params.getMtanNumberId(), params.getUserId()});
         this.repository.store(params.getUserId(), item);
      }

      if (!params.getPhoneNumber().hasValue() || StringUtils.isEmpty((CharSequence)params.getPhoneNumber().value())) {
         item = (MtanItem)this.repository.getMtanToken(params.getUserId(), params.getMtanNumberId()).orElseThrow();
         return Optional.of(MtanTokenService.AlteredPhoneNumberResult.noNumberChange(this.createViewOf(item), mtanDataChanges));
      } else {
         return StringUtils.isEmpty(item.getMobilePhoneNumber())
            ? Optional.of(this.registerPhoneNumber(item, (String)params.getPhoneNumber().value(), params.getUserId()))
            : Optional.of(
               this.changePhoneNumber(
                  MtanTokenService.ChangePhoneNumberParams.builder()
                     .mtanNumberId(params.getMtanNumberId())
                     .newPhoneNumber((String)params.getPhoneNumber().value())
                     .userId(params.getUserId())
                     .build()
               )
            );
      }
   }

   public MtanTokenService.AlteredPhoneNumberResult changePhoneNumber(MtanTokenService.ChangePhoneNumberParams params) {
      Optional<MtanItem> mtanItem = this.repository.getMtanToken(params.getUserId(), params.getMtanNumberId());
      if (mtanItem.isEmpty()) {
         this.signingLogger
            .log(
               "Service failure while changing phone number of mTAN item \"%s\" for user \"%s\", mTAN item does not exist.",
               new Object[]{params.mtanNumberId, params.userId}
            );
         throw new ViolationException(String.format("MtanItem '%s' does not exist", params.getMtanNumberId()));
      }

      String oldPhoneNumberNormalized = this.normalizePhoneNumber(mtanItem.get().getMobilePhoneNumber());
      String newPhoneNumberNormalized = this.normalizePhoneNumber(params.getNewPhoneNumber());
      if (Strings.CS.equals(oldPhoneNumberNormalized, newPhoneNumberNormalized)) {
         return MtanTokenService.AlteredPhoneNumberResult.noNumberChange(this.createViewOf(mtanItem.get()), MtanTokenService.MtanDataChanges.noChange());
      }

      mtanItem.get().setMobilePhoneNumber(newPhoneNumberNormalized);
      this.repository.store(params.getUserId(), mtanItem.get());
      String logMessage = this.getNumberChangeLogMessage(oldPhoneNumberNormalized, newPhoneNumberNormalized);
      Optional<OneTimePassword> oneTimePassword = this.sendOtpToNewNumberIfConfigured(newPhoneNumberNormalized);
      Optional<OneTimePasswordVerificationContext> otpVerification = oneTimePassword.<Otp>map(OneTimePassword::value)
         .map(this.oneTimePasswordService::createVerificationContext);
      this.sendNotificationToOldNumber(oldPhoneNumberNormalized, newPhoneNumberNormalized);
      this.publishMtanTokenPhoneNumberChanged(
         params.getUserId(),
         mtanItem.get().getMtanNumberId(),
         PhoneNumber.phoneNumber(oldPhoneNumberNormalized),
         PhoneNumber.phoneNumber(newPhoneNumberNormalized)
      );
      this.signingLogger.log(logMessage + " for user " + LogUtil.quote(params.userId) + ".");
      return MtanTokenService.AlteredPhoneNumberResult.builder()
         .oneTimePassword(otpVerification)
         .mtanItem(this.createViewOf(mtanItem.get()))
         .logMessage(Optional.of(logMessage))
         .build();
   }

   private void publishMtanTokenPhoneNumberChanged(
      UserId userId, MtanNumberId mtanNumberId, PhoneNumber oldPhoneNumberNormalized, PhoneNumber newPhoneNumberNormalized
   ) {
      ((MtanTokenEventFactory)this.eventFactory.get()).publishMtanNumberChangedEvent(userId, mtanNumberId, oldPhoneNumberNormalized, newPhoneNumberNormalized);
   }

   private String getNumberChangeLogMessage(String oldNumber, String newNumber) {
      return "Administrator "
         + LogUtil.quote(this.administrator.getName())
         + " changed mobile phone number from "
         + LogUtil.quote(this.phoneNumberService.maskPhoneNumber(oldNumber))
         + " to "
         + LogUtil.quote(this.phoneNumberService.maskPhoneNumber(newNumber));
   }

   private String getRegisterNumberLogMessage(String newNumber) {
      return "Administrator "
         + LogUtil.quote(this.administrator.getName())
         + " registered mobile phone number "
         + LogUtil.quote(this.phoneNumberService.maskPhoneNumber(newNumber));
   }

   private Optional<OneTimePassword> sendOtpToNewNumberIfConfigured(String newPhoneNumber) {
      if (!this.mtanSettings.isNotifyNewNumber()) {
         return Optional.empty();
      }

      String message = this.mtanSettings.getNotificationSmsTemplate();
      Optional<OneTimePassword> otp = Optional.empty();
      if (Strings.CS.contains(message, "$TOKEN$")) {
         otp = Optional.of(this.oneTimePasswordService.create());
         message = message.replace("$TOKEN$", otp.get().value().rawValue());
      }

      Sms sms = Sms.builder()
         .originator(this.mtanSettings.getOriginatorName())
         .phoneNumber(newPhoneNumber)
         .message(message)
         .flashMessage(this.mtanSettings.isUseFlashMessages())
         .build();
      this.smsService.send(sms);
      return otp;
   }

   private void sendNotificationToOldNumber(String oldNumber, String newNumber) {
      if (this.tokenController.getNotifyOldNumberTemplate().isPresent()) {
         String message = this.tokenController.getNotifyOldNumberTemplate().get().replace("$OLDNUMBER$", oldNumber);
         message = message.replace("$NEWNUMBER$", newNumber);
         Sms sms = Sms.builder()
            .originator(this.mtanSettings.getOriginatorName())
            .phoneNumber(oldNumber)
            .message(message)
            .flashMessage(this.mtanSettings.isUseFlashMessages())
            .build();
         this.smsService.send(sms);
      }
   }

   public boolean supportsLabel() {
      return this.mtanHandler().supportsLabel();
   }

   public MtanItemView changeLabel(MtanTokenService.ChangeLabelParams params) throws ViolationException {
      if (!this.supportsLabel()) {
         throw new ViolationException("The underlying mTAN handler does not support labels");
      }

      Optional<MtanItem> mtanItem = this.repository.getMtanToken(params.getUserId(), params.getMtanNumberId());
      if (mtanItem.isEmpty()) {
         throw new ViolationException(String.format("MtanItem '%s' does not exist", params.getMtanNumberId()));
      }

      mtanItem.get().setLabel(params.getNewLabel());
      this.repository.store(params.getUserId(), mtanItem.get());
      this.signingLogger.log("Changed label of mTAN item \"%s\" for user \"%s\"", new Object[]{params.mtanNumberId, params.userId});
      return this.createViewOf(mtanItem.get());
   }

   public void deleteMtanItem(@NonNull MtanTokenService.DeleteMtanItemParams params) {
      if (params == null) {
         throw new NullPointerException("params is marked non-null but is null");
      }

      Optional<MtanItem> mtanItem = this.repository.getMtanToken(params.getUserId(), params.getMtanNumberId());
      if (mtanItem.isEmpty()) {
         this.signingLogger
            .log("Service failure while deleting mTAN item \"%s\" for user \"%s\", mTAN item does not exist.", new Object[]{params.mtanNumberId, params.userId});
         throw new ViolationException(String.format("MtanItem '%s' does not exist", params.getMtanNumberId()));
      }

      String oldNumber = mtanItem.get().getMobilePhoneNumber();
      this.repository.delete(params.getUserId(), mtanItem.get());
      this.publishMtanTokenDeleted(params.getUserId(), mtanItem.get().getMtanNumberId(), oldNumber);
      this.signingLogger.log("Deleted mTAN item \"%s\" for user \"%s\"", new Object[]{params.getMtanNumberId(), params.getUserId()});
   }

   private void publishMtanTokenDeleted(UserId userId, MtanNumberId itemId, @Nullable String oldNumber) {
      if (oldNumber != null && this.phoneNumberService.isValidPhoneNumber(oldNumber)) {
         ((MtanTokenEventFactory)this.eventFactory.get())
            .publishMtanTokenDeletedEvent(userId, itemId, PhoneNumber.phoneNumber(this.normalizePhoneNumber(oldNumber)));
      }
   }

   public void sendSms(MtanTokenService.SendSmsParams params) {
      Preconditions.checkNotNull(params);
      Optional<MtanItem> mtanItem = this.repository.getMtanToken(params.getUserId(), params.getMtanNumberId());
      if (mtanItem.isEmpty()) {
         String msg = String.format("mTAN token %s for user %s not found", params.getMtanNumberId(), params.getUserId());
         LOG.debug(msg);
         throw new ViolationException(msg);
      }

      Sms sms = this.buildSms(params, mtanItem).build();
      this.smsService.send(sms);
      this.signingLogger
         .log(
            "SMS sent to mobile phone \"%s\" of user \"%s\"",
            new Object[]{this.phoneNumberService.maskPhoneNumber(mtanItem.get().getMobilePhoneNumber()), params.getUserId()}
         );
   }

   public boolean isValid(MtanTokenService.SendSmsParams params) {
      Optional<MtanItem> mtanItem = this.repository.getMtanToken(params.getUserId(), params.getMtanNumberId());
      if (mtanItem.isEmpty()) {
         return false;
      }

      Sms sms = this.buildSms(params, mtanItem).build();
      return this.smsService.isValid(sms);
   }

   public boolean isValidForOtp(MtanTokenService.SendSmsParams params) {
      Optional<MtanItem> mtanItem = this.repository.getMtanToken(params.getUserId(), params.getMtanNumberId());
      if (mtanItem.isEmpty()) {
         return false;
      }

      SmsBuilder builder = this.buildSms(params, mtanItem);
      if (params.getSmsText() == null) {
         builder.message(this.mtanSettings.getNotificationSmsTemplate());
      }

      Sms sms = builder.build();
      return this.smsService.isValid(sms);
   }

   private SmsBuilder buildSms(MtanTokenService.SendSmsParams params, Optional<MtanItem> mtanItemOptional) {
      MtanItem mtanItem = mtanItemOptional.orElseThrow();
      boolean mtanItemFlash;
      if (mtanItem.isSendSmsAsFlashConfigurable() && params.hasFlashMessage()) {
         mtanItemFlash = params.getFlashMessage();
      } else {
         mtanItemFlash = mtanItem.getSendSmsAsFlash() != null ? mtanItem.getSendSmsAsFlash() : this.mtanSettings.isUseFlashMessages();
      }

      return Sms.builder()
         .originator(params.getOriginator() != null ? params.getOriginator() : this.mtanSettings.getOriginatorName())
         .phoneNumber(mtanItem.getMobilePhoneNumber())
         .message(params.getSmsText())
         .flashMessage(mtanItemFlash);
   }

   public OneTimePasswordVerificationContext sendOtp(MtanTokenService.SendSmsParams params) {
      Preconditions.checkNotNull(params);
      if (params.getSmsText().contains("$TOKEN$")) {
         OneTimePassword otp = this.oneTimePasswordService.create();
         this.sendSms(params.withSmsText(Strings.CS.replace(params.getSmsText(), "$TOKEN$", otp.value().rawValue())));
         this.signingLogger.log("OTP sent to mTAN token \"%s\" of user \"%s\"", new Object[]{params.getMtanNumberId(), params.getUserId()});
         return this.oneTimePasswordService.createVerificationContext(otp.value());
      } else {
         this.sendSms(params);
         this.signingLogger.log("Test SMS (without OTP) sent to mTAN token \"%s\" of user \"%s\"", new Object[]{params.getMtanNumberId(), params.getUserId()});
         return this.oneTimePasswordService.createExpiredVerificationContext();
      }
   }

   public OneTimePasswordService.VerificationResult verifyOtp(String otp, OneTimePasswordVerificationContext otpVerification) {
      Preconditions.checkArgument(StringUtils.isNotBlank(otp));
      Preconditions.checkNotNull(otpVerification);
      this.signingLogger.log("Verifying OTP value");
      return this.oneTimePasswordService.verify(otp, otpVerification);
   }

   public Optional<Iak> getIak(UserId userId) {
      return this.repository.getIak(userId);
   }

   public void generateIak(UserId userId) {
      if (this.tokenController.getSecretLetterRenderer().isEmpty()) {
         throw new FeatureDisabledException(userId.getValue(), "No mTAN secret letter rendered configured to generate the IAK letter");
      }

      MtanItemBackedIak iak = this.getOrCreateIak(userId);
      iak.generate();
      this.repository.store(userId, iak);
      this.signingLogger.log("Generated IAK for user \"%s\"", new Object[]{userId});
   }

   public void orderIak(UserId userId) {
      this.mtanTokenOrderService.orderIak(userId);
   }

   public void cancelIakOrder(UserId userId) {
      this.mtanTokenOrderService.cancelIakOrder(userId);
   }

   public ValidationResult validate(MtanTokenService.MtanItemParams item) {
      return item.getPhoneNumber().hasValue() && !this.phoneNumberService.isValidPhoneNumber((String)item.getPhoneNumber().value())
         ? ValidationResult.singleError("phoneNumber", GenericError.WRONG_FORMAT)
         : ValidationResult.valid();
   }

   private MtanItemBackedIak getOrCreateIak(UserId userId) {
      Optional<Iak> iakOptional = this.repository.getIak(userId);
      return iakOptional.isPresent() ? (MtanItemBackedIak)iakOptional.get() : (MtanItemBackedIak)this.repository.createIak(userId);
   }

   private MtanItemView createViewOf(MtanItem mtanItem) {
      return MtanItemView.of(mtanItem, this.phoneNumberService);
   }

   private Optional<MtanItemView> createOptionalViewOf(Optional<MtanItem> mtanItem) {
      return mtanItem.isPresent() ? Optional.of(this.createViewOf(mtanItem.get())) : Optional.empty();
   }

   private MtanHandler mtanHandler() {
      return this.mtanSettings.getMtanHandler();
   }

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

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

   public static final class AlteredPhoneNumberResult {
      private final MtanItemView mtanItem;
      private final Optional<OneTimePasswordVerificationContext> oneTimePassword;
      private final Optional<String> logMessage;

      public static MtanTokenService.AlteredPhoneNumberResult noNumberChange(MtanItemView mtanItem, MtanTokenService.MtanDataChanges mtanDataChanges) {
         return builder().mtanItem(mtanItem).logMessage(buildLogMessage(mtanItem, mtanDataChanges)).oneTimePassword(Optional.empty()).build();
      }

      private static Optional<String> buildLogMessage(MtanItemView mtanItem, MtanTokenService.MtanDataChanges mtanDataChanges) {
         return !mtanDataChanges.hasChanges()
            ? Optional.empty()
            : Optional.of(
               "Administrator ${authentee} updated "
                  + logDataChange(mtanDataChanges)
                  + " data of mTAN item with ID '"
                  + mtanItem.getId()
                  + "' of user ${affectedUser}."
            );
      }

      private static String logDataChange(MtanTokenService.MtanDataChanges mtanDataChanges) {
         if (mtanDataChanges.isFlashUpdated() && mtanDataChanges.isLabelUpdated()) {
            return "label and flash";
         } else {
            return !mtanDataChanges.isFlashUpdated() ? "label" : "flash";
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      AlteredPhoneNumberResult(MtanItemView mtanItem, Optional<OneTimePasswordVerificationContext> oneTimePassword, Optional<String> logMessage) {
         this.mtanItem = mtanItem;
         this.oneTimePassword = oneTimePassword;
         this.logMessage = logMessage;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static MtanTokenService.AlteredPhoneNumberResult.AlteredPhoneNumberResultBuilder builder() {
         return new MtanTokenService.AlteredPhoneNumberResult.AlteredPhoneNumberResultBuilder();
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanItemView getMtanItem() {
         return this.mtanItem;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Optional<OneTimePasswordVerificationContext> getOneTimePassword() {
         return this.oneTimePassword;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Optional<String> getLogMessage() {
         return this.logMessage;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof MtanTokenService.AlteredPhoneNumberResult other)) {
            return false;
         } else {
            Object this$mtanItem = this.mtanItem;
            Object other$mtanItem = other.mtanItem;
            if (this$mtanItem == null ? other$mtanItem == null : this$mtanItem.equals(other$mtanItem)) {
               Object this$oneTimePassword = this.oneTimePassword;
               Object other$oneTimePassword = other.oneTimePassword;
               if (this$oneTimePassword == null ? other$oneTimePassword == null : this$oneTimePassword.equals(other$oneTimePassword)) {
                  Object this$logMessage = this.logMessage;
                  Object other$logMessage = other.logMessage;
                  return this$logMessage == null ? other$logMessage == null : this$logMessage.equals(other$logMessage);
               } else {
                  return false;
               }
            } else {
               return false;
            }
         }
      }

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanTokenService.AlteredPhoneNumberResult(mtanItem="
            + this.getMtanItem()
            + ", oneTimePassword="
            + this.getOneTimePassword()
            + ", logMessage="
            + this.getLogMessage()
            + ")";
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class AlteredPhoneNumberResultBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private MtanItemView mtanItem;
         @SuppressFBWarnings(justification = "generated code")
         private Optional<OneTimePasswordVerificationContext> oneTimePassword;
         @SuppressFBWarnings(justification = "generated code")
         private Optional<String> logMessage;

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.AlteredPhoneNumberResult.AlteredPhoneNumberResultBuilder mtanItem(MtanItemView mtanItem) {
            this.mtanItem = mtanItem;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.AlteredPhoneNumberResult.AlteredPhoneNumberResultBuilder oneTimePassword(
            Optional<OneTimePasswordVerificationContext> oneTimePassword
         ) {
            this.oneTimePassword = oneTimePassword;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.AlteredPhoneNumberResult.AlteredPhoneNumberResultBuilder logMessage(Optional<String> logMessage) {
            this.logMessage = logMessage;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.AlteredPhoneNumberResult build() {
            return new MtanTokenService.AlteredPhoneNumberResult(this.mtanItem, this.oneTimePassword, this.logMessage);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MtanTokenService.AlteredPhoneNumberResult.AlteredPhoneNumberResultBuilder(mtanItem="
               + this.mtanItem
               + ", oneTimePassword="
               + this.oneTimePassword
               + ", logMessage="
               + this.logMessage
               + ")";
         }
      }
   }

   public static final class ChangeLabelParams {
      private final UserId userId;
      private final String mtanNumberId;
      @Nullable
      private final String newLabel;

      @SuppressFBWarnings(justification = "generated code")
      ChangeLabelParams(UserId userId, String mtanNumberId, @Nullable String newLabel) {
         this.userId = userId;
         this.mtanNumberId = mtanNumberId;
         this.newLabel = newLabel;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static MtanTokenService.ChangeLabelParams.ChangeLabelParamsBuilder builder() {
         return new MtanTokenService.ChangeLabelParams.ChangeLabelParamsBuilder();
      }

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

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

      @Nullable
      @SuppressFBWarnings(justification = "generated code")
      public String getNewLabel() {
         return this.newLabel;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof MtanTokenService.ChangeLabelParams 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$mtanNumberId = this.mtanNumberId;
               Object other$mtanNumberId = other.mtanNumberId;
               if (this$mtanNumberId == null ? other$mtanNumberId == null : this$mtanNumberId.equals(other$mtanNumberId)) {
                  Object this$newLabel = this.newLabel;
                  Object other$newLabel = other.newLabel;
                  return this$newLabel == null ? other$newLabel == null : this$newLabel.equals(other$newLabel);
               } 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 $mtanNumberId = this.mtanNumberId;
         result = result * 59 + ($mtanNumberId == null ? 43 : $mtanNumberId.hashCode());
         Object $newLabel = this.newLabel;
         return result * 59 + ($newLabel == null ? 43 : $newLabel.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanTokenService.ChangeLabelParams(userId="
            + this.getUserId()
            + ", mtanNumberId="
            + this.getMtanNumberId()
            + ", newLabel="
            + this.getNewLabel()
            + ")";
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class ChangeLabelParamsBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private UserId userId;
         @SuppressFBWarnings(justification = "generated code")
         private String mtanNumberId;
         @SuppressFBWarnings(justification = "generated code")
         private String newLabel;

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

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.ChangeLabelParams.ChangeLabelParamsBuilder mtanNumberId(String mtanNumberId) {
            this.mtanNumberId = mtanNumberId;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.ChangeLabelParams.ChangeLabelParamsBuilder newLabel(@Nullable String newLabel) {
            this.newLabel = newLabel;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.ChangeLabelParams build() {
            return new MtanTokenService.ChangeLabelParams(this.userId, this.mtanNumberId, this.newLabel);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MtanTokenService.ChangeLabelParams.ChangeLabelParamsBuilder(userId="
               + this.userId
               + ", mtanNumberId="
               + this.mtanNumberId
               + ", newLabel="
               + this.newLabel
               + ")";
         }
      }
   }

   public static final class ChangePhoneNumberParams {
      private final UserId userId;
      private final String mtanNumberId;
      private final String newPhoneNumber;

      @SuppressFBWarnings(justification = "generated code")
      ChangePhoneNumberParams(UserId userId, String mtanNumberId, String newPhoneNumber) {
         this.userId = userId;
         this.mtanNumberId = mtanNumberId;
         this.newPhoneNumber = newPhoneNumber;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static MtanTokenService.ChangePhoneNumberParams.ChangePhoneNumberParamsBuilder builder() {
         return new MtanTokenService.ChangePhoneNumberParams.ChangePhoneNumberParamsBuilder();
      }

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof MtanTokenService.ChangePhoneNumberParams 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$mtanNumberId = this.mtanNumberId;
               Object other$mtanNumberId = other.mtanNumberId;
               if (this$mtanNumberId == null ? other$mtanNumberId == null : this$mtanNumberId.equals(other$mtanNumberId)) {
                  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 $mtanNumberId = this.mtanNumberId;
         result = result * 59 + ($mtanNumberId == null ? 43 : $mtanNumberId.hashCode());
         Object $newPhoneNumber = this.newPhoneNumber;
         return result * 59 + ($newPhoneNumber == null ? 43 : $newPhoneNumber.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanTokenService.ChangePhoneNumberParams(userId="
            + this.getUserId()
            + ", mtanNumberId="
            + this.getMtanNumberId()
            + ", newPhoneNumber="
            + this.getNewPhoneNumber()
            + ")";
      }

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

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

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.ChangePhoneNumberParams.ChangePhoneNumberParamsBuilder mtanNumberId(String mtanNumberId) {
            this.mtanNumberId = mtanNumberId;
            return this;
         }

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.ChangePhoneNumberParams build() {
            return new MtanTokenService.ChangePhoneNumberParams(this.userId, this.mtanNumberId, this.newPhoneNumber);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MtanTokenService.ChangePhoneNumberParams.ChangePhoneNumberParamsBuilder(userId="
               + this.userId
               + ", mtanNumberId="
               + this.mtanNumberId
               + ", newPhoneNumber="
               + this.newPhoneNumber
               + ")";
         }
      }
   }

   public static final class DeleteMtanItemParams {
      private final UserId userId;
      private final String mtanNumberId;

      @SuppressFBWarnings(justification = "generated code")
      DeleteMtanItemParams(UserId userId, String mtanNumberId) {
         this.userId = userId;
         this.mtanNumberId = mtanNumberId;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static MtanTokenService.DeleteMtanItemParams.DeleteMtanItemParamsBuilder builder() {
         return new MtanTokenService.DeleteMtanItemParams.DeleteMtanItemParamsBuilder();
      }

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

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof MtanTokenService.DeleteMtanItemParams 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$mtanNumberId = this.mtanNumberId;
               Object other$mtanNumberId = other.mtanNumberId;
               return this$mtanNumberId == null ? other$mtanNumberId == null : this$mtanNumberId.equals(other$mtanNumberId);
            } 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 $mtanNumberId = this.mtanNumberId;
         return result * 59 + ($mtanNumberId == null ? 43 : $mtanNumberId.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanTokenService.DeleteMtanItemParams(userId=" + this.getUserId() + ", mtanNumberId=" + this.getMtanNumberId() + ")";
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class DeleteMtanItemParamsBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private UserId userId;
         @SuppressFBWarnings(justification = "generated code")
         private String mtanNumberId;

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

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.DeleteMtanItemParams.DeleteMtanItemParamsBuilder mtanNumberId(String mtanNumberId) {
            this.mtanNumberId = mtanNumberId;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.DeleteMtanItemParams build() {
            return new MtanTokenService.DeleteMtanItemParams(this.userId, this.mtanNumberId);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MtanTokenService.DeleteMtanItemParams.DeleteMtanItemParamsBuilder(userId=" + this.userId + ", mtanNumberId=" + this.mtanNumberId + ")";
         }
      }
   }

   private static class MtanDataChanges {
      private boolean labelUpdated;
      private boolean flashUpdated;

      boolean hasChanges() {
         return this.labelUpdated || this.flashUpdated;
      }

      static MtanTokenService.MtanDataChanges noChange() {
         return new MtanTokenService.MtanDataChanges();
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanDataChanges() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public boolean isLabelUpdated() {
         return this.labelUpdated;
      }

      @SuppressFBWarnings(justification = "generated code")
      public boolean isFlashUpdated() {
         return this.flashUpdated;
      }

      @SuppressFBWarnings(justification = "generated code")
      public void setLabelUpdated(boolean labelUpdated) {
         this.labelUpdated = labelUpdated;
      }

      @SuppressFBWarnings(justification = "generated code")
      public void setFlashUpdated(boolean flashUpdated) {
         this.flashUpdated = flashUpdated;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof MtanTokenService.MtanDataChanges other)) {
            return false;
         } else if (!other.canEqual(this)) {
            return false;
         } else {
            return this.labelUpdated != other.labelUpdated ? false : this.flashUpdated == other.flashUpdated;
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      protected boolean canEqual(Object other) {
         return other instanceof MtanTokenService.MtanDataChanges;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         result = result * 59 + (this.labelUpdated ? 79 : 97);
         return result * 59 + (this.flashUpdated ? 79 : 97);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanTokenService.MtanDataChanges(labelUpdated=" + this.isLabelUpdated() + ", flashUpdated=" + this.isFlashUpdated() + ")";
      }
   }

   public static final class MtanItemParams {
      private final UserId userId;
      @Nullable
      private final String mtanNumberId;
      private final OptionalString phoneNumber;
      private final OptionalString label;
      private final OptionalBoolean flash;

      private MtanItemParams() {
         this.userId = null;
         this.mtanNumberId = null;
         this.phoneNumber = OptionalString.none();
         this.label = OptionalString.none();
         this.flash = OptionalBoolean.none();
      }

      public static MtanTokenService.MtanItemParams.MtanItemParamsBuilder builder() {
         return new MtanTokenService.MtanItemParams().toBuilder();
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenService.MtanItemParams.MtanItemParamsBuilder toBuilder() {
         return new MtanTokenService.MtanItemParams.MtanItemParamsBuilder()
            .userId(this.userId)
            .mtanNumberId(this.mtanNumberId)
            .phoneNumber(this.phoneNumber)
            .label(this.label)
            .flash(this.flash);
      }

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

      @Nullable
      @SuppressFBWarnings(justification = "generated code")
      public String getMtanNumberId() {
         return this.mtanNumberId;
      }

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

      @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 MtanItemParams(UserId userId, @Nullable String mtanNumberId, OptionalString phoneNumber, OptionalString label, OptionalBoolean flash) {
         this.userId = userId;
         this.mtanNumberId = mtanNumberId;
         this.phoneNumber = phoneNumber;
         this.label = label;
         this.flash = flash;
      }

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

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

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.MtanItemParams.MtanItemParamsBuilder mtanNumberId(@Nullable String mtanNumberId) {
            this.mtanNumberId = mtanNumberId;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.MtanItemParams.MtanItemParamsBuilder phoneNumber(OptionalString phoneNumber) {
            this.phoneNumber = phoneNumber;
            return this;
         }

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

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.MtanItemParams build() {
            return new MtanTokenService.MtanItemParams(this.userId, this.mtanNumberId, this.phoneNumber, this.label, this.flash);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MtanTokenService.MtanItemParams.MtanItemParamsBuilder(userId="
               + this.userId
               + ", mtanNumberId="
               + this.mtanNumberId
               + ", phoneNumber="
               + this.phoneNumber
               + ", label="
               + this.label
               + ", flash="
               + this.flash
               + ")";
         }
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class MtanTokenServiceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private MtanSettings mtanSettings;
      @SuppressFBWarnings(justification = "generated code")
      private SmsService smsService;
      @SuppressFBWarnings(justification = "generated code")
      private OneTimePasswordService oneTimePasswordService;
      @SuppressFBWarnings(justification = "generated code")
      private PhoneNumberService phoneNumberService;
      @SuppressFBWarnings(justification = "generated code")
      private MtanTokenController tokenController;
      @SuppressFBWarnings(justification = "generated code")
      private CurrentAdministrator administrator;
      @SuppressFBWarnings(justification = "generated code")
      private SigningLogger signingLogger;
      @SuppressFBWarnings(justification = "generated code")
      private MtanTokenRepository repository;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<MtanTokenEventFactory> eventFactory;

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

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenService.MtanTokenServiceBuilder mtanSettings(MtanSettings mtanSettings) {
         this.mtanSettings = mtanSettings;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenService.MtanTokenServiceBuilder oneTimePasswordService(OneTimePasswordService oneTimePasswordService) {
         this.oneTimePasswordService = oneTimePasswordService;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenService.MtanTokenServiceBuilder tokenController(MtanTokenController tokenController) {
         this.tokenController = tokenController;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenService.MtanTokenServiceBuilder administrator(CurrentAdministrator administrator) {
         this.administrator = administrator;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenService.MtanTokenServiceBuilder repository(MtanTokenRepository repository) {
         this.repository = repository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenService.MtanTokenServiceBuilder eventFactory(Provider<MtanTokenEventFactory> eventFactory) {
         this.eventFactory = eventFactory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenService build() {
         return new MtanTokenService(
            this.mtanSettings,
            this.smsService,
            this.oneTimePasswordService,
            this.phoneNumberService,
            this.tokenController,
            this.administrator,
            this.signingLogger,
            this.repository,
            this.eventFactory
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanTokenService.MtanTokenServiceBuilder(mtanSettings="
            + this.mtanSettings
            + ", smsService="
            + this.smsService
            + ", oneTimePasswordService="
            + this.oneTimePasswordService
            + ", phoneNumberService="
            + this.phoneNumberService
            + ", tokenController="
            + this.tokenController
            + ", administrator="
            + this.administrator
            + ", signingLogger="
            + this.signingLogger
            + ", repository="
            + this.repository
            + ", eventFactory="
            + this.eventFactory
            + ")";
      }
   }

   public static final class SendSmsParams {
      private final String originator;
      private final UserId userId;
      private final String mtanNumberId;
      private final String smsText;
      private final Boolean flashMessage;

      public boolean hasFlashMessage() {
         return this.flashMessage != null;
      }

      @SuppressFBWarnings(justification = "generated code")
      SendSmsParams(String originator, UserId userId, String mtanNumberId, String smsText, Boolean flashMessage) {
         this.originator = originator;
         this.userId = userId;
         this.mtanNumberId = mtanNumberId;
         this.smsText = smsText;
         this.flashMessage = flashMessage;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static MtanTokenService.SendSmsParams.SendSmsParamsBuilder builder() {
         return new MtanTokenService.SendSmsParams.SendSmsParamsBuilder();
      }

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

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof MtanTokenService.SendSmsParams other)) {
            return false;
         } else {
            Object this$flashMessage = this.flashMessage;
            Object other$flashMessage = other.flashMessage;
            if (this$flashMessage == null ? other$flashMessage == null : this$flashMessage.equals(other$flashMessage)) {
               Object this$originator = this.originator;
               Object other$originator = other.originator;
               if (this$originator == null ? other$originator == null : this$originator.equals(other$originator)) {
                  Object this$userId = this.userId;
                  Object other$userId = other.userId;
                  if (this$userId == null ? other$userId == null : this$userId.equals(other$userId)) {
                     Object this$mtanNumberId = this.mtanNumberId;
                     Object other$mtanNumberId = other.mtanNumberId;
                     if (this$mtanNumberId == null ? other$mtanNumberId == null : this$mtanNumberId.equals(other$mtanNumberId)) {
                        Object this$smsText = this.smsText;
                        Object other$smsText = other.smsText;
                        return this$smsText == null ? other$smsText == null : this$smsText.equals(other$smsText);
                     } 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 $flashMessage = this.flashMessage;
         result = result * 59 + ($flashMessage == null ? 43 : $flashMessage.hashCode());
         Object $originator = this.originator;
         result = result * 59 + ($originator == null ? 43 : $originator.hashCode());
         Object $userId = this.userId;
         result = result * 59 + ($userId == null ? 43 : $userId.hashCode());
         Object $mtanNumberId = this.mtanNumberId;
         result = result * 59 + ($mtanNumberId == null ? 43 : $mtanNumberId.hashCode());
         Object $smsText = this.smsText;
         return result * 59 + ($smsText == null ? 43 : $smsText.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanTokenService.SendSmsParams(originator="
            + this.getOriginator()
            + ", userId="
            + this.getUserId()
            + ", mtanNumberId="
            + this.getMtanNumberId()
            + ", smsText="
            + this.getSmsText()
            + ", flashMessage="
            + this.getFlashMessage()
            + ")";
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenService.SendSmsParams withSmsText(String smsText) {
         return this.smsText == smsText
            ? this
            : new MtanTokenService.SendSmsParams(this.originator, this.userId, this.mtanNumberId, smsText, this.flashMessage);
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class SendSmsParamsBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private String originator;
         @SuppressFBWarnings(justification = "generated code")
         private UserId userId;
         @SuppressFBWarnings(justification = "generated code")
         private String mtanNumberId;
         @SuppressFBWarnings(justification = "generated code")
         private String smsText;
         @SuppressFBWarnings(justification = "generated code")
         private Boolean flashMessage;

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.SendSmsParams.SendSmsParamsBuilder originator(String originator) {
            this.originator = originator;
            return this;
         }

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

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.SendSmsParams.SendSmsParamsBuilder mtanNumberId(String mtanNumberId) {
            this.mtanNumberId = mtanNumberId;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.SendSmsParams.SendSmsParamsBuilder smsText(String smsText) {
            this.smsText = smsText;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.SendSmsParams.SendSmsParamsBuilder flashMessage(Boolean flashMessage) {
            this.flashMessage = flashMessage;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MtanTokenService.SendSmsParams build() {
            return new MtanTokenService.SendSmsParams(this.originator, this.userId, this.mtanNumberId, this.smsText, this.flashMessage);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MtanTokenService.SendSmsParams.SendSmsParamsBuilder(originator="
               + this.originator
               + ", userId="
               + this.userId
               + ", mtanNumberId="
               + this.mtanNumberId
               + ", smsText="
               + this.smsText
               + ", flashMessage="
               + this.flashMessage
               + ")";
         }
      }
   }
}
