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.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.phonenumber.PhoneNumber;
import com.airlock.iam.common.domain.model.mtan.event.MtanTokenEventFactory;
import com.airlock.iam.common.domain.model.token.mtan.CommonMtanTokenRepository;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
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.authen.MtanUsedDeviceInfo;
import com.airlock.iam.core.misc.impl.token.MtanItem;
import com.airlock.iam.flow.api.domain.model.session.FlowAttributeRepository;
import com.airlock.iam.flow.shared.api.domain.model.session.ChangeDescriptionFlowAttribute;
import com.airlock.iam.flow.shared.application.configuration.step.apply.ApplyMtanRegistrationChangeConfig;
import com.airlock.iam.flow.shared.domain.model.attributes.AuthTokenIdFlowSessionAttribute;
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(ApplyMtanRegistrationChangeConfig.class)
public final class ApplyMtanRegistrationChange implements ApplyChangeHandler<MtanTokenChangeDescription> {
   private final Provider<PrincipalInfo> principalInfo;
   private final Provider<Optional<UserPrincipalService>> userPrincipalService;
   private final Provider<PhoneNumberService> phoneNumberService;
   private final Provider<CommonMtanTokenRepository> repository;
   private final SigningLogger signingLogger;
   private final HelpdeskLogger helpdeskLogger;
   private final Provider<MtanTokenEventFactory> eventFactory;
   private final Provider<FlowAttributeRepository> attributeRepository;
   private final Provider<MtanRegistrationSettings> settings;

   @Inject
   public ApplyMtanRegistrationChange(
      Provider<PrincipalInfo> principalInfo,
      Provider<Optional<UserPrincipalService>> userPrincipalService,
      Provider<CommonMtanTokenRepository> repository,
      Provider<PhoneNumberService> phoneNumberService,
      SigningLogger signingLogger,
      HelpdeskLogger helpdeskLogger,
      Provider<MtanTokenEventFactory> eventFactory,
      Provider<FlowAttributeRepository> attributeRepository,
      Provider<MtanRegistrationSettings> settings
   ) {
      this.principalInfo = principalInfo;
      this.userPrincipalService = userPrincipalService;
      this.repository = repository;
      this.phoneNumberService = phoneNumberService;
      this.signingLogger = signingLogger;
      this.helpdeskLogger = helpdeskLogger;
      this.eventFactory = eventFactory;
      this.attributeRepository = attributeRepository;
      this.settings = settings;
   }

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

      if (change.getNewPhoneNumber().isEmpty()) {
         throw new FailureException("Cannot persist mTAN registration change without a number");
      }

      PhoneNumber newPhoneNumber = ((PhoneNumberService)this.phoneNumberService.get()).normalizePhoneNumber(change.getNewPhoneNumber().get());
      MtanItem item = this.createMtanItem(change.getNewLabel(), newPhoneNumber);
      this.persist(item);
      this.saveAuthTokenId(item);
      this.publishMtanTokenRegistered(item.getMtanNumberId(), newPhoneNumber);
      this.logChange(item);
      return Collections.emptyMap();
   }

   private MtanItem createMtanItem(OptionalString label, PhoneNumber newPhoneNumber) {
      MtanItem item = ((CommonMtanTokenRepository)this.repository.get()).createMtanItem(this.userId(), this.userId().getValue());
      item.setMobilePhoneNumber(newPhoneNumber.getValue());
      label.ifPresent(item::setLabel);
      return item;
   }

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

   private void saveAuthTokenId(MtanItem item) {
      if (((MtanRegistrationSettings)this.settings.get()).saveAuthTokenId()) {
         AuthTokenIdFlowSessionAttribute authTokenId = AuthTokenIdFlowSessionAttribute.authTokenIdFlowSessionAttribute(MtanUsedDeviceInfo.of(item).toString());
         ((FlowAttributeRepository)this.attributeRepository.get()).putOrMergeAttribute(authTokenId);
      }
   }

   private void publishMtanTokenRegistered(MtanNumberId mtanNumberId, PhoneNumber newPhoneNumber) {
      ((MtanTokenEventFactory)this.eventFactory.get()).publishMtanTokenRegisteredEvent(this.userId(), mtanNumberId, newPhoneNumber);
   }

   private void logChange(MtanItem item) {
      String maskedPhoneNumber = ((PhoneNumberService)this.phoneNumberService.get()).maskPhoneNumber(item.getMobilePhoneNumber());
      this.signingLogger.log(String.format("User %s successfully registered mTAN number %s", LogUtil.quote(this.userId()), LogUtil.quote(maskedPhoneNumber)));
      this.helpdeskLogger.log(this.userId(), String.format("Successfully registered mTAN number %s", LogUtil.quote(maskedPhoneNumber)));
   }

   private UserId userId() {
      return ((PrincipalInfo)this.principalInfo.get()).getKnownIdentity().orElseThrow().id();
   }

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