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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.common.api.domain.model.otp.UnverifiedOtp;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.core.misc.tokenverifier.TokenVerifier;
import com.airlock.iam.core.misc.tokenverifier.TokenVerifierException;
import com.airlock.iam.core.misc.tokenverifier.TokenVerifierResponse;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.log.api.signing.SigningLogger;
import jakarta.inject.Inject;
import jakarta.inject.Provider;

public final class TokenVerifierAdapter implements OtpVerificationService {
   private final Provider<PrincipalInfo> user;
   private final Provider<TokenVerifier> verifier;
   private final Provider<OtpHistoryService> otpHistory;
   private final Provider<SigningLogger> signingLogger;
   private final Provider<HelpdeskLogger> helpdeskLogger;

   @Inject
   public TokenVerifierAdapter(
      Provider<PrincipalInfo> user,
      Provider<TokenVerifier> verifier,
      Provider<OtpHistoryService> otpHistory,
      Provider<SigningLogger> signingLogger,
      Provider<HelpdeskLogger> helpdeskLogger
   ) {
      this.user = user;
      this.verifier = verifier;
      this.otpHistory = otpHistory;
      this.signingLogger = signingLogger;
      this.helpdeskLogger = helpdeskLogger;
   }

   @Override
   public boolean hasToken() {
      return ((TokenVerifier)this.verifier.get()).hasToken(this.userId().getValue());
   }

   @Override
   public OtpVerificationService.OtpVerificationResult verifyOtp(UnverifiedOtp otp) {
      try {
         UserId userId = this.userId();
         if (((OtpHistoryService)this.otpHistory.get()).otpAlreadyUsed(userId, otp)) {
            this.log("OTP has already been used, next OTP required.", userId);
            return OtpVerificationService.OtpVerificationResult.NEXT_OTP_REQUIRED;
         }

         TokenVerifierResponse response = ((TokenVerifier)this.verifier.get())
            .verifyToken(((TokenVerifier)this.verifier.get()).createNewSession(), userId.getValue(), otp.rawValue());
         if (response == TokenVerifierResponse.TOKEN_ACCEPTED) {
            ((OtpHistoryService)this.otpHistory.get()).rememberUsedOtp(userId, otp);
         }

         return this.mapResponse(response, userId);
      } catch (TokenVerifierException e) {
         throw new FailureException(e);
      }
   }

   private OtpVerificationService.OtpVerificationResult mapResponse(TokenVerifierResponse response, UserId userId) {
      return switch (response) {
         case TOKEN_ACCEPTED -> {
            this.log(String.format("OTP accepted for user '%s'", userId.getValue()), userId);
            yield OtpVerificationService.OtpVerificationResult.OTP_ACCEPTED;
         }
         case TOKEN_DENIED -> {
            this.log(String.format("OTP denied for user '%s'", userId.getValue()), userId);
            yield OtpVerificationService.OtpVerificationResult.OTP_REJECTED;
         }
         case NEXT_TOKEN_REQUIRED -> {
            this.log(String.format("Next OTP required for user '%s'", userId.getValue()), userId);
            yield OtpVerificationService.OtpVerificationResult.NEXT_OTP_REQUIRED;
         }
         case TOKEN_INACTIVE -> {
            this.log(String.format("Token inactive for user '%s'", userId.getValue()), userId);
            yield OtpVerificationService.OtpVerificationResult.TOKEN_INACTIVE;
         }
         case NEW_PIN_REQUIRED, NEW_PIN_ACCEPTED, UNKNOWN -> throw new FailureException("unexpected verifier response: " + response);
      };
   }

   private UserId userId() {
      return ((PrincipalInfo)this.user.get()).getKnownIdentity().orElseThrow(() -> new FailureException("User must be identified")).id();
   }

   private void log(String message, UserId userId) {
      ((HelpdeskLogger)this.helpdeskLogger.get()).log(userId, message);
      ((SigningLogger)this.signingLogger.get()).log(message);
   }
}
