package com.airlock.iam.flow.shared.domain.service.mtan;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.domain.model.user.UserId;
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.phonenumber.PhoneNumber;
import com.airlock.iam.common.domain.model.mtan.event.MtanTokenEventFactory;
import com.airlock.iam.common.domain.model.principal.UserPrincipal;
import com.airlock.iam.common.domain.model.token.mtan.CommonMtanTokenRepository;
import com.airlock.iam.common.domain.service.phone.PhoneNumberService;
import com.airlock.iam.common.domain.service.principal.UserPrincipalService;
import com.airlock.iam.core.misc.impl.token.MtanItem;
import com.airlock.iam.flow.shared.api.domain.model.session.ChangeDescriptionFlowAttribute;
import com.airlock.iam.flow.shared.application.configuration.step.apply.ApplyMtanEditChangeConfig;
import com.airlock.iam.flow.shared.domain.model.mtan.MtanTokenChangeDescription;
import com.airlock.iam.flow.shared.domain.model.step.apply.ApplyChangeHandler;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.log.api.signing.SigningLogger;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;

@LogicForConfig(ApplyMtanEditChangeConfig.class)
public final class ApplyMtanEditChange implements ApplyChangeHandler<MtanTokenChangeDescription> {
   private final Provider<UserPrincipal> userPrincipal;
   private final Provider<UserPrincipalService> userPrincipalService;
   private final Provider<CommonMtanTokenRepository> tokenRepository;
   private final Provider<PhoneNumberService> phoneNumberService;
   private final Provider<MtanTokenEventFactory> eventFactory;
   private final SigningLogger signingLogger;
   private final HelpdeskLogger helpdeskLogger;

   @Inject
   public ApplyMtanEditChange(
      Provider<UserPrincipal> userPrincipal,
      Provider<UserPrincipalService> userPrincipalService,
      Provider<CommonMtanTokenRepository> tokenRepository,
      Provider<PhoneNumberService> phoneNumberService,
      Provider<MtanTokenEventFactory> eventFactory,
      SigningLogger signingLogger,
      HelpdeskLogger helpdeskLogger
   ) {
      this.userPrincipal = userPrincipal;
      this.userPrincipalService = userPrincipalService;
      this.tokenRepository = tokenRepository;
      this.phoneNumberService = phoneNumberService;
      this.eventFactory = eventFactory;
      this.signingLogger = signingLogger;
      this.helpdeskLogger = helpdeskLogger;
   }

   public Map<String, Object> applyChange(MtanTokenChangeDescription change) {
      if (change.getExistingToken().isEmpty()) {
         throw new FailureException("Cannot persist mTAN edit change without an existing item");
      }

      Optional<PhoneNumber> newPhoneNumber = change.getNewPhoneNumber().map(this::normalize);
      boolean numberChange = newPhoneNumber.isPresent();
      boolean labelChange = change.getNewLabel().hasValue();
      if (!labelChange && !numberChange) {
         return Collections.emptyMap();
      }

      CommonMtanTokenRepository repository = (CommonMtanTokenRepository)this.tokenRepository.get();
      MtanItem item = repository.findUniqueMtanToken(this.userId(), change.getExistingToken().get().getTokenId());
      PhoneNumber currentPhoneNumber = this.normalize(PhoneNumber.phoneNumber(item.getMobilePhoneNumber()));
      String currentPhoneNumberMasked = ((PhoneNumberService)this.phoneNumberService.get()).maskPhoneNumber(currentPhoneNumber.getValue());
      String currentLabel = item.getLabel();
      if (numberChange) {
         item.setMobilePhoneNumber(newPhoneNumber.get().getValue());
      }

      if (labelChange) {
         item.setLabel(change.getNewLabel().value());
      }

      this.save(repository, item);
      if (labelChange) {
         if (change.getNewLabel().value() == null) {
            this.signingLogger
               .log(
                  String.format(
                     "User %s successfully removed mTAN label for phone number %s", LogUtil.quote(this.userId()), LogUtil.quote(currentPhoneNumberMasked)
                  )
               );
            this.helpdeskLogger
               .log(this.userId(), String.format("Successfully removed mTAN label for phone number %s", LogUtil.quote(currentPhoneNumberMasked)));
         } else {
            this.signingLogger
               .log(
                  String.format(
                     "User %s successfully changed mTAN label from %s to %s for phone number %s",
                     LogUtil.quote(this.userId()),
                     LogUtil.quote(currentLabel),
                     LogUtil.quote(change.getNewLabel().value()),
                     LogUtil.quote(currentPhoneNumberMasked)
                  )
               );
            this.helpdeskLogger
               .log(
                  this.userId(),
                  String.format(
                     "Successfully changed mTAN label from %s to %s for phone number %s",
                     LogUtil.quote(currentLabel),
                     LogUtil.quote(change.getNewLabel().value()),
                     LogUtil.quote(currentPhoneNumberMasked)
                  )
               );
         }
      }

      if (numberChange) {
         String newPhoneNumberMasked = ((PhoneNumberService)this.phoneNumberService.get()).maskPhoneNumber(newPhoneNumber.get().getValue());
         this.publishMtanTokenNumberChanged(this.userId(), item.getMtanNumberId(), currentPhoneNumber, newPhoneNumber.get());
         this.signingLogger
            .log(
               String.format(
                  "User %s successfully changed mTAN number from %s to %s",
                  LogUtil.quote(this.userId()),
                  LogUtil.quote(currentPhoneNumberMasked),
                  LogUtil.quote(newPhoneNumberMasked)
               )
            );
         this.helpdeskLogger
            .log(
               this.userId(),
               String.format("Successfully changed mTAN number from %s to %s", LogUtil.quote(currentPhoneNumberMasked), LogUtil.quote(newPhoneNumberMasked))
            );
      }

      return Collections.emptyMap();
   }

   private void publishMtanTokenNumberChanged(UserId userId, MtanNumberId mtanNumberId, PhoneNumber oldNumber, PhoneNumber newNumber) {
      ((MtanTokenEventFactory)this.eventFactory.get()).publishMtanNumberChangedEvent(userId, mtanNumberId, oldNumber, newNumber);
   }

   private PhoneNumber normalize(PhoneNumber number) {
      return ((PhoneNumberService)this.phoneNumberService.get()).normalizePhoneNumber(number);
   }

   private void save(CommonMtanTokenRepository repository, MtanItem item) {
      if (repository.isUserContextDataAffected()) {
         ((UserPrincipalService)this.userPrincipalService.get()).savePrincipal();
         repository.store(this.userId(), item);
         ((UserPrincipalService)this.userPrincipalService.get()).reloadPrincipal();
      } else {
         repository.store(this.userId(), item);
      }
   }

   private UserId userId() {
      return ((UserPrincipal)this.userPrincipal.get()).getId();
   }

   @Override
   public <S extends ChangeDescriptionFlowAttribute<S>> boolean handlesChangeOfType(Class<S> type) {
      return type.isAssignableFrom(MtanTokenChangeDescription.class);
   }
}
