package com.airlock.iam.factor.infrastructure.rest.airlock2fa;

import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FAAccountId;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.common.infrastructure.restclient.RestClient;
import com.airlock.iam.common.infrastructure.restclient.RestRequest;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthDevice;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthenticationException;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAMobileOnlyRedirectUri;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAPreAuthenticationData;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FASessionId;
import com.airlock.iam.factor.domain.model.airlock2fa.ImmediateAuthenticationAnswer;
import com.airlock.iam.factor.domain.model.airlock2fa.MobileOnlyMessageApprovalAnswer;
import com.airlock.iam.factor.domain.model.airlock2fa.OfflineQrCodeMessageApprovalAnswer;
import com.airlock.iam.factor.domain.model.airlock2fa.OneTouchAndOfflineQrCodeMessageApprovalAnswer;
import com.airlock.iam.factor.domain.model.airlock2fa.OneTouchMessageApprovalAnswer;
import com.airlock.iam.factor.domain.model.airlock2fa.OnlineQrCodeMessageApprovalAnswer;
import com.airlock.iam.factor.domain.model.airlock2fa.futurae.Airlock2FAFuturaeAuthSession;
import com.airlock.iam.factor.domain.model.airlock2fa.futurae.FuturaeErrorResponseException;
import com.airlock.iam.factor.domain.model.airlock2fa.otp.Airlock2FAOtp;
import com.airlock.iam.factor.domain.model.airlock2fa.otp.Airlock2FAPasscode;
import com.airlock.iam.factor.domain.model.airlock2fa.qrcode.Airlock2FAExchangeTokensAnswer;
import com.airlock.iam.factor.domain.model.airlock2fa.qrcode.Airlock2FAQrCodeImage;
import com.airlock.iam.factor.domain.model.airlock2fa.qrcode.Airlock2FAQrCodeUrl;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAApprovalService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAAuthenticationService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAPreAuthenticationService;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeApprovalAnswerReaders;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeExchangeTokenAnswerReaders;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeMessageApprovalRequestFactory;
import com.airlock.iam.flow.shared.api.domain.model.message.Airlock2FAMessage;
import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.time.Duration;
import java.util.Optional;

public final class Airlock2FAApprovalServiceImpl implements Airlock2FAApprovalService {
   private static final ELogger LOG = ELoggerFactory.getLogger(Airlock2FAApprovalServiceImpl.class);
   private final Provider<Airlock2FAAuthenticationService> authenticationService;
   private final Provider<FuturaeMessageApprovalRequestFactory> requestFactory;
   private final Provider<RestClient> restClient;
   private final Provider<FuturaeApprovalAnswerReaders> answerReaders;
   private final Provider<FuturaeExchangeTokenAnswerReaders> exchangeTokenAnswerReaders;

   @Inject
   public Airlock2FAApprovalServiceImpl(
      Provider<Airlock2FAAuthenticationService> authenticationService,
      Provider<FuturaeMessageApprovalRequestFactory> requestFactory,
      Provider<RestClient> restClient,
      Provider<FuturaeApprovalAnswerReaders> answerReaders,
      Provider<FuturaeExchangeTokenAnswerReaders> exchangeTokenAnswerReaders
   ) {
      this.authenticationService = authenticationService;
      this.requestFactory = requestFactory;
      this.restClient = restClient;
      this.answerReaders = answerReaders;
      this.exchangeTokenAnswerReaders = exchangeTokenAnswerReaders;
   }

   @Override
   public Airlock2FAPreAuthenticationService.PreAuthenticationAnswer preAuthenticate(Airlock2FAPreAuthenticationData authenticationData) {
      try {
         return ((Airlock2FAAuthenticationService)this.authenticationService.get()).preAuthenticate(authenticationData);
      } catch (Airlock2FAAuthenticationException e) {
         throw new FailureException(String.format("Could not pre-authenticate user with Airlock 2FA account %s", authenticationData.accountId()), e);
      }
   }

   @Override
   public OneTouchAndOfflineQrCodeMessageApprovalAnswer approveWithOneTouchAndOfflineQrCode(
      Airlock2FAAccountId accountId, Airlock2FAAuthDevice device, Airlock2FAMessage message
   ) {
      LOG.debug("Requesting approval with One-Touch and Offline QR Code fallback for account: " + accountId);

      try {
         RestRequest request = ((FuturaeMessageApprovalRequestFactory)this.requestFactory.get())
            .approveTransactionWithOneTouchAndOfflineQrCodeRequestFor(accountId, device, message);
         return ((RestClient)this.restClient.get())
            .read(request, ((FuturaeApprovalAnswerReaders)this.answerReaders.get()).forOneTouchAndOfflineQrCode(request));
      } catch (Exception e) {
         throw new FailureException(String.format("Could not approve for user with Airlock 2FA account %s with One-Touch and Offline QR Code", accountId), e);
      }
   }

   @Override
   public OneTouchMessageApprovalAnswer approveWithOneTouch(Airlock2FAAccountId accountId, Airlock2FAAuthDevice device, Airlock2FAMessage message) {
      LOG.debug("Requesting approval with One-Touch for account: " + accountId);

      try {
         RestRequest request = ((FuturaeMessageApprovalRequestFactory)this.requestFactory.get())
            .approveTransactionWithPushRequestFor(accountId, device, message);
         return ((RestClient)this.restClient.get()).read(request, ((FuturaeApprovalAnswerReaders)this.answerReaders.get()).forOneTouch(request));
      } catch (Exception e) {
         throw new FailureException(String.format("Could not approve for user with Airlock 2FA id %s with One-Touch", accountId), e);
      }
   }

   @Override
   public OneTouchMessageApprovalAnswer approveWithPushToAll(Airlock2FAAccountId accountId, Airlock2FAMessage message) {
      LOG.debug("Requesting approval with One-Touch for account: " + accountId);

      try {
         RestRequest request = ((FuturaeMessageApprovalRequestFactory)this.requestFactory.get()).approveTransactionWithPushToAllRequestFor(accountId, message);
         return ((RestClient)this.restClient.get()).read(request, ((FuturaeApprovalAnswerReaders)this.answerReaders.get()).forOneTouch(request));
      } catch (Exception e) {
         throw new FailureException(String.format("Could not approve for user with Airlock 2FA id %s with One-Touch (Push-to-All)", accountId), e);
      }
   }

   @Override
   public OfflineQrCodeMessageApprovalAnswer approveWithOfflineQrCode(Airlock2FAAccountId accountId, Airlock2FAAuthDevice device, Airlock2FAMessage message) {
      LOG.debug("Requesting approval with Offline QR Code for account: " + accountId);

      try {
         RestRequest request = ((FuturaeMessageApprovalRequestFactory)this.requestFactory.get())
            .approveTransactionWithOfflineQrCodeRequestFor(accountId, device, message);
         return ((RestClient)this.restClient.get()).read(request, ((FuturaeApprovalAnswerReaders)this.answerReaders.get()).forOfflineQrCode(request));
      } catch (Exception e) {
         throw new FailureException("Could not approve with Offline QR Code for user with Airlock 2FA account: " + accountId, e);
      }
   }

   @Override
   public OnlineQrCodeMessageApprovalAnswer approveWithOnlineQrCode(
      Airlock2FAAccountId accountId,
      Optional<Airlock2FAMobileOnlyRedirectUri> mobileOnlyRedirectUri,
      Airlock2FAMessage message,
      Optional<Duration> sessionTimeout
   ) {
      LOG.debug("Requesting approval with Online QR Code for account: " + accountId);

      try {
         RestRequest request = ((FuturaeMessageApprovalRequestFactory)this.requestFactory.get())
            .approveTransactionWithOnlineQrCodeRequestFor(accountId, mobileOnlyRedirectUri, message, sessionTimeout);
         return ((RestClient)this.restClient.get()).read(request, ((FuturaeApprovalAnswerReaders)this.answerReaders.get()).forOnlineQrCode(request));
      } catch (Exception e) {
         throw new FailureException("Could not approve with Online QR Code for user with Airlock 2FA account: " + accountId, e);
      }
   }

   @Override
   public MobileOnlyMessageApprovalAnswer approveWithMobileOnly(
      Airlock2FAAccountId accountId, Optional<Airlock2FAMobileOnlyRedirectUri> mobileOnlyRedirectUri, Airlock2FAMessage message
   ) {
      LOG.debug("Requesting approval with Mobile Only for account: " + accountId);

      try {
         RestRequest request = ((FuturaeMessageApprovalRequestFactory)this.requestFactory.get())
            .approveTransactionWithMobileOnlyRequestFor(accountId, mobileOnlyRedirectUri, message);
         return ((RestClient)this.restClient.get()).read(request, ((FuturaeApprovalAnswerReaders)this.answerReaders.get()).forMobileOnly(request));
      } catch (Exception e) {
         throw new FailureException("Could not approve with Mobile Only for user with Airlock 2FA account: " + accountId, e);
      }
   }

   @Override
   public Airlock2FAAuthenticationService.AuthenticationStatusAnswer checkTransactionAuthenticationStatus(Airlock2FAFuturaeAuthSession session) {
      try {
         return ((Airlock2FAAuthenticationService)this.authenticationService.get()).checkAuthenticationStatus(session);
      } catch (Airlock2FAAuthenticationException e) {
         throw new FailureException("Could not check approval status of user with Airlock 2FA account: " + session.getAccountId(), e);
      }
   }

   @Override
   public Result<ImmediateAuthenticationAnswer, FuturaeErrorResponseException> abortTransaction(Airlock2FAAccountId accountId, Airlock2FASessionId sessionId) {
      return ((Airlock2FAAuthenticationService)this.authenticationService.get()).abortAuthentication(accountId, sessionId);
   }

   @Override
   public Airlock2FAQrCodeImage retrieveOfflineQrCodeImage(Airlock2FAAccountId accountId, Airlock2FAQrCodeUrl url) {
      try {
         return ((Airlock2FAAuthenticationService)this.authenticationService.get()).retrieveOfflineQrCodeImage(accountId, url);
      } catch (Airlock2FAAuthenticationException e) {
         throw new FailureException("Could not retrieve Offline QR Code image for user with Airlock 2FA account: " + accountId, e);
      }
   }

   @Override
   public Result<Airlock2FAAuthenticationService.ValidateOfflineQrCodeAnswer, FuturaeErrorResponseException> validateOfflineQrCode(
      Airlock2FAAccountId accountId, Airlock2FASessionId sessionId, Airlock2FAOtp otp
   ) {
      return ((Airlock2FAAuthenticationService)this.authenticationService.get()).validateOfflineQrCode(accountId, sessionId, otp);
   }

   @Override
   public ImmediateAuthenticationAnswer approveWithPasscode(Airlock2FAAccountId accountId, Airlock2FAPasscode passcode) {
      return ((Airlock2FAAuthenticationService)this.authenticationService.get()).authenticateWithPasscode(accountId, passcode);
   }

   @Override
   public Airlock2FAExchangeTokensAnswer fetchAuthenticationExchangeTokens(
      Airlock2FAAccountId accountId,
      Airlock2FASessionId sessionId,
      Duration exchangeTokenLifetime,
      int totalExchangeTokens,
      String uriSchema,
      String associatedDomain
   ) {
      try {
         RestRequest request = ((FuturaeMessageApprovalRequestFactory)this.requestFactory.get())
            .fetchAuthenticationExchangeTokensRequest(accountId, sessionId, exchangeTokenLifetime, totalExchangeTokens, uriSchema, associatedDomain);
         return ((RestClient)this.restClient.get())
            .read(request, ((FuturaeExchangeTokenAnswerReaders)this.exchangeTokenAnswerReaders.get()).forSessionExchangeTokens(request));
      } catch (JsonProcessingException e) {
         throw Airlock2FAAuthenticationException.airlock2FAAuthenticationException(
            String.format("Could not fetch exchange tokens for user with Airlock 2FA account %s", accountId), e
         );
      }
   }
}
