package com.airlock.iam.publicselfservice.domain.model.step;

import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResult;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultFactory;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.authmethod.AuthenticationMethod;
import com.airlock.iam.common.api.domain.model.otp.OneTimePassword;
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.otp.OneTimePasswordFactory;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactorDetail;
import com.airlock.iam.core.misc.impl.token.MtanItem;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.step.StepSession;
import com.airlock.iam.publicselfservice.application.configuration.steps.SmsIdentityVerificationStepConfig;
import com.airlock.iam.publicselfservice.domain.model.flow.session.OtpIdentityVerificationStepSession;
import com.airlock.iam.publicselfservice.domain.model.mtan.PublicSelfServiceUserWithMtan;
import com.airlock.iam.publicselfservice.domain.service.SmsIdentityVerificationService;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;

@LogicForConfig(SmsIdentityVerificationStepConfig.class)
public final class SmsIdentityVerificationStepImpl implements MtanIdentityVerificationStep {
   private static final ELogger LOG = ELoggerFactory.getLogger(SmsIdentityVerificationStepImpl.class);
   private final Provider<OtpIdentityVerificationStepSession> session;
   private final Provider<PublicSelfServiceUserWithMtan> user;
   private final Provider<OneTimePasswordFactory> otpFactory;
   private final SmsIdentityVerificationStepSettings settings;
   private final Provider<SmsIdentityVerificationService> service;

   @Inject
   public SmsIdentityVerificationStepImpl(
      Provider<OtpIdentityVerificationStepSession> session,
      Provider<PublicSelfServiceUserWithMtan> user,
      Provider<OneTimePasswordFactory> otpFactory,
      SmsIdentityVerificationStepSettings settings,
      Provider<SmsIdentityVerificationService> service
   ) {
      this.session = session;
      this.user = user;
      this.otpFactory = otpFactory;
      this.settings = settings;
      this.service = service;
   }

   @Override
   public Optional<StepSession> createSession() {
      return Optional.of(new OtpIdentityVerificationStepSession(this.settings.getMaxFailedAttempts()));
   }

   @Override
   public AuthenticationStepResult initialize() {
      if (!((PublicSelfServiceUserWithMtan)this.user.get()).isPublicSelfServiceAllowed()) {
         LOG.info("Not sending SMS because user " + this.quotedName() + " doesn't exist or is not allowed to do public self-service.");
         return this.simulateMtanVerification();
      } else {
         Optional<MtanItem> mtanItem = ((PublicSelfServiceUserWithMtan)this.user.get()).getMtanItem();
         if (mtanItem.isEmpty()) {
            LOG.info("Not sending SMS because user " + this.quotedName() + " doesn't have a valid mobile device.");
            return this.simulateMtanVerification();
         } else {
            OneTimePassword otp = ((OneTimePasswordFactory)this.otpFactory.get()).createOneTimePassword();
            ((OtpIdentityVerificationStepSession)this.session.get()).setOtp(otp);
            ((SmsIdentityVerificationService)this.service.get()).sendSms(mtanItem.get(), otp.value());
            LOG.info("Sent SMS with OTP to user " + this.quotedName());
            return this.stepResultFactory().stay(MtanIdentityVerificationStep.MtanOtpStepAction.MTAN_OTP_REQUIRED).build();
         }
      }
   }

   public AuthenticationStepResult verifyMtanOtp(UnverifiedOtp otp) {
      if (!((OtpIdentityVerificationStepSession)this.session.get()).hasOtp()) {
         return this.stepResultFactory().failDueToUnexpectedCall("no OTP").build();
      }

      return switch (((OtpIdentityVerificationStepSession)this.session.get())
         .getOtp()
         .orElseThrow()
         .verify(otp, OtpEqualityStrategy.equalityStrategy(!this.settings.isOtpCaseSensitive()))) {
         case EXPIRED -> this.expiredOtp();
         case WRONG_VALUE -> this.incorrectOtp();
         case SUCCESS -> this.correctOtp();
      };
   }

   private AuthenticationStepResult expiredOtp() {
      return this.stepResultFactory().fail(MtanIdentityVerificationStep.MtanOtpStepError.MTAN_OTP_EXPIRED).build();
   }

   private AuthenticationStepResult incorrectOtp() {
      ((OtpIdentityVerificationStepSession)this.session.get()).increaseFailedAttempts();
      if (!((OtpIdentityVerificationStepSession)this.session.get()).isRetryPossible()) {
         LOG.info("User " + this.quotedName() + " failed to verify OTP, no further attempts left");
         return this.stepResultFactory().fail(MtanIdentityVerificationStep.MtanOtpStepError.MTAN_OTP_WRONG).build();
      } else {
         LOG.info(
            "User "
               + this.quotedName()
               + " failed to verify OTP, remaining attempts: "
               + ((OtpIdentityVerificationStepSession)this.session.get()).getRemainingAttempts()
         );
         return this.stepResultFactory()
            .failRetry(MtanIdentityVerificationStep.MtanOtpStepError.MTAN_OTP_WRONG, MtanIdentityVerificationStep.MtanOtpStepAction.MTAN_OTP_REQUIRED)
            .build();
      }
   }

   private AuthenticationStepResult correctOtp() {
      if (((PublicSelfServiceUserWithMtan)this.user.get()).isPublicSelfServiceAllowed()) {
         return this.stepResultFactory().success().build();
      }

      LOG.info("SMS identity verification failed for user " + this.quotedName() + " because the public self-service is not allowed");
      return this.stepResultFactory().fail(MtanIdentityVerificationStep.MtanOtpStepError.MTAN_OTP_WRONG).isFailedFactor(false).build();
   }

   @Override
   public String getDescription() {
      return "SMS Identity Verification Step";
   }

   private String quotedName() {
      return LogUtil.logSafeQuote(((PublicSelfServiceUserWithMtan)this.user.get()).getIdentity().id());
   }

   private AuthenticationStepResult simulateMtanVerification() {
      ((OtpIdentityVerificationStepSession)this.session.get()).setOtp(((OneTimePasswordFactory)this.otpFactory.get()).createSimulatedOneTimePassword());
      return this.stepResultFactory().stay(MtanIdentityVerificationStep.MtanOtpStepAction.MTAN_OTP_REQUIRED).build();
   }

   @Override
   public AuthenticationMethod getAuthenticationMethod() {
      return this.settings.getAuthMethodId();
   }

   public AuthenticationStepResultFactory stepResultFactory() {
      return AuthenticationStepResultFactory.forFactorDetail(KnownAuthenticationFactorDetail.OTP_MTAN);
   }
}
