package com.airlock.iam.authentication.domain.model.airlock2fa;

import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FAAccountId;
import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FADeviceId;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationFlowStep;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResult;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultFactory;
import com.airlock.iam.authentication.api.domain.model.step.IdentityProvidingFlowStep;
import com.airlock.iam.authentication.api.domain.model.step.UserIdentifyingStep;
import com.airlock.iam.authentication.api.domain.service.username.IdentificationResult;
import com.airlock.iam.authentication.api.domain.service.username.IdentificationResultConverter;
import com.airlock.iam.authentication.api.domain.service.username.IdentityProvidingService;
import com.airlock.iam.authentication.application.configuration.airlock2fa.Airlock2FAUsernamelessQrCodeAuthenticationStepConfig;
import com.airlock.iam.authentication.impl.domain.model.authentication.UserIdentificationResultData;
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.base.misc.result.Result;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.common.api.domain.model.authmethod.AuthenticationMethod;
import com.airlock.iam.common.api.domain.model.device.KnownDeviceTypes;
import com.airlock.iam.common.api.domain.model.device.usage.AuthenticationDeviceUsage;
import com.airlock.iam.common.api.domain.model.user.ProvidedUsername;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactorDetail;
import com.airlock.iam.core.domain.model.authentication.method.KnownAuthenticationMethods;
import com.airlock.iam.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FADeviceInformation;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactor;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FASessionId;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAUser;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAUserInformation;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAUserPresenceVerification;
import com.airlock.iam.factor.domain.model.airlock2fa.AuthenticationStatus;
import com.airlock.iam.factor.domain.model.airlock2fa.DeferredAuthenticationAnswer;
import com.airlock.iam.factor.domain.model.airlock2fa.event.Airlock2FADeviceEventFactory;
import com.airlock.iam.factor.domain.model.airlock2fa.futurae.FuturaeErrorResponseException;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAAuthenticationService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FACooldownInformationService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FALogHelper;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAUserInformationService;
import com.airlock.iam.factor.domain.service.airlock2fa.AuthenticationStatusService;
import com.airlock.iam.factor.domain.service.airlock2fa.repository.Airlock2FAUserRepository;
import com.airlock.iam.flow.api.domain.model.step.StepAction;
import com.airlock.iam.flow.api.domain.model.step.StepError;
import com.airlock.iam.flow.api.domain.model.step.StepSession;
import com.airlock.iam.flow.shared.api.domain.model.message.Airlock2FAMessage;
import com.airlock.iam.flow.shared.api.domain.model.message.Airlock2FAMessageProvider;
import com.airlock.iam.flow.shared.api.domain.model.message.Airlock2FAMessageProviderException;
import com.airlock.iam.flow.shared.domain.service.PollingService;
import com.airlock.iam.flow.shared.domain.service.airlock2fa.Airlock2FAAuthTokenIdService;
import com.airlock.iam.flow.shared.domain.service.airlock2fa.Airlock2FAFlowSessionAttributesService;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.time.Duration;
import java.util.Optional;
import java.util.function.Function;

@LogicForConfig(Airlock2FAUsernamelessQrCodeAuthenticationStepConfig.class)
public final class Airlock2FAUsernamelessQrCodeAuthenticationStep implements IdentityProvidingFlowStep, Airlock2FAAuthenticationStep {
   private static final ELogger LOG = ELoggerFactory.getLogger(Airlock2FAUsernamelessQrCodeAuthenticationStep.class);
   private static final String CHALLENGE_IMAGE_KEY = "challengeImage";
   private final Provider<Airlock2FAAuthenticationService> service;
   private final Provider<Optional<Airlock2FAUsernamelessQrCodeAuthenticationStepSession>> stepSession;
   private final Provider<AuthenticationStatusService> authenticationStatusService;
   private final Provider<Airlock2FAUserRepository> airlock2FAUserRepositoryProvider;
   private final Provider<Airlock2FAUserInformationService> airlock2FAUserInformationService;
   private final Provider<IdentityProvidingService> identityProvidingService;
   private final Provider<Airlock2FAAuthTokenIdService> authTokenIdService;
   private final Provider<Airlock2FALogHelper> logHelper;
   private final Provider<Airlock2FAUsernamelessStepSettings> settings;
   private final Provider<TimeProvider> time;
   private final Provider<Optional<Airlock2FAMessageProvider>> messageProvider;
   private final Provider<Airlock2FADeviceEventFactory> eventFactory;
   private final Provider<Airlock2FAFlowSessionAttributesService> attributes;
   private final Provider<Airlock2FACooldownInformationService> cooldownService;
   private final PollingService<Airlock2FAUsernamelessQrCodeAuthenticationStepSession, AuthenticationStepResult> pollingService;

   @Inject
   public Airlock2FAUsernamelessQrCodeAuthenticationStep(
      Provider<Airlock2FAAuthenticationService> service,
      Provider<Optional<Airlock2FAUsernamelessQrCodeAuthenticationStepSession>> stepSession,
      Provider<AuthenticationStatusService> authenticationStatusService,
      Provider<Airlock2FAUserRepository> airlock2FAUserRepositoryProvider,
      Provider<Airlock2FAUserInformationService> airlock2FAUserInformationService,
      Provider<IdentityProvidingService> identityProvidingService,
      Provider<Airlock2FAAuthTokenIdService> authTokenIdService,
      Provider<Airlock2FALogHelper> logHelper,
      Provider<Airlock2FAUsernamelessStepSettings> settings,
      Provider<TimeProvider> time,
      Provider<Optional<Airlock2FAMessageProvider>> messageProvider,
      Provider<Airlock2FADeviceEventFactory> eventFactory,
      Provider<Airlock2FAFlowSessionAttributesService> attributes,
      Provider<Airlock2FACooldownInformationService> cooldownService
   ) {
      this.service = service;
      this.stepSession = stepSession;
      this.authenticationStatusService = authenticationStatusService;
      this.airlock2FAUserRepositoryProvider = airlock2FAUserRepositoryProvider;
      this.airlock2FAUserInformationService = airlock2FAUserInformationService;
      this.identityProvidingService = identityProvidingService;
      this.authTokenIdService = authTokenIdService;
      this.logHelper = logHelper;
      this.settings = settings;
      this.time = time;
      this.messageProvider = messageProvider;
      this.eventFactory = eventFactory;
      this.attributes = attributes;
      this.cooldownService = cooldownService;
      this.pollingService = new PollingService<>(
         Airlock2FAUsernamelessQrCodeAuthenticationStepSession::getPollingCache, this::pollInternal, this::pollingRequired
      );
   }

   @Override
   public Optional<StepSession> createSession() {
      return Optional.of(Airlock2FAUsernamelessQrCodeAuthenticationStepSession.emptySession());
   }

   @Override
   public AuthenticationStepResult initialize() {
      this.fetchQrCode();
      return this.pollingRequired(this.stepSession());
   }

   public AuthenticationStepResult pollAuthenticationStatus(boolean queryOnly) {
      return this.pollingService.poll(this.stepSession(), queryOnly);
   }

   private AuthenticationStepResult pollInternal(Airlock2FAUsernamelessQrCodeAuthenticationStepSession session) {
      Optional<Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException>> previousStatus = session.getPreviousSessionId()
         .map(this::checkStatus);
      this.logStatusAnswer(previousStatus);
      if (isFinalDeny(previousStatus)) {
         return this.handleDeny(previousStatus.orElseThrow());
      }

      Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException> currentStatus = this.checkStatus(
         session.getCurrentSessionId().orElseThrow()
      );
      this.logStatusAnswer(currentStatus);
      if (isFinalDeny(currentStatus)) {
         return this.handleDeny(currentStatus);
      }

      if (isSuccess(previousStatus)) {
         return this.handleSuccess(previousStatus.orElseThrow().get());
      }

      if (isSuccess(currentStatus)) {
         return this.handleSuccess(currentStatus.get());
      }

      if (isTimeout(currentStatus) && !this.isRenewalDue()) {
         return this.timeoutResult();
      }

      if (this.isRenewalDue()) {
         this.fetchQrCode();
      }

      if (isTimeout(currentStatus)) {
         session.clearPreviousSession();
      }

      return this.pollingRequired(session);
   }

   private AuthenticationStepResult handleSuccess(Airlock2FAAuthenticationService.AuthenticationStatusAnswer answer) {
      Result<Optional<UserIdentity>, AuthenticationStepResult> identificationResult = this.tryIdentifyUser(answer);
      if (identificationResult.isFailure()) {
         return identificationResult.getFailure();
      }

      answer.getStatus().ifPresent(((AuthenticationStatusService)this.authenticationStatusService.get())::handleStatus);
      Airlock2FAAccountId accountId = answer.getAccountId().orElseThrow(() -> new FailureException("Expected Account ID in response with result=\"allow\""));
      Airlock2FAUserInformation userInfo = this.fetchUserInfo(accountId);
      Optional<Duration> cooldownPeriod = ((Airlock2FAUsernamelessStepSettings)this.settings.get()).deviceSettings().cooldownSettings().cooldownPeriod();
      if (cooldownPeriod.isPresent()) {
         UserId userId = identificationResult.get().map(UserIdentity::id).orElseThrow();
         Optional<AuthenticationStepResult> failureResult = this.validateCooldownPeriod(answer, userInfo, cooldownPeriod.get(), userId, accountId);
         if (failureResult.isPresent()) {
            return failureResult.get();
         }
      }

      this.handleAuthenticationSuccess(answer, accountId, userInfo);
      return this.successResult(
         identificationResult.get().orElseThrow(),
         answer.getDeviceId().orElseThrow(() -> new FailureException("Expected Device ID in response with result=\"allow\""))
      );
   }

   private Optional<AuthenticationStepResult> validateCooldownPeriod(
      Airlock2FAAuthenticationService.AuthenticationStatusAnswer answer,
      Airlock2FAUserInformation userInfo,
      Duration cooldownPeriod,
      UserId userId,
      Airlock2FAAccountId accountId
   ) {
      Airlock2FADeviceId usedDeviceId = answer.getDeviceId().orElseThrow(() -> new FailureException("Expected Device ID in response with result=\"allow\""));
      if (isDeviceCurrentlyInCooldownPeriod(userInfo, cooldownPeriod, usedDeviceId)) {
         if (((Airlock2FAUsernamelessStepSettings)this.settings.get()).deviceSettings().cooldownSettings().cooldownConfiguredAndRespected()) {
            ((Airlock2FALogHelper)this.logHelper.get()).logFailedDueToCooldownPeriod("authentication", answer.getDeviceId().orElseThrow());
            return Optional.of(
               this.stepResultFactory().fail(AuthenticationFlowStep.GeneralAuthenticationStepError.AUTHENTICATION_FAILED).isFailedFactor(false).build()
            );
         }

         this.publishDeviceInCooldownUsedEvent(userId, accountId, answer.getDeviceId().orElseThrow());
      }

      return Optional.empty();
   }

   private static boolean isDeviceCurrentlyInCooldownPeriod(Airlock2FAUserInformation userInfo, Duration cooldownPeriod, Airlock2FADeviceId deviceId) {
      return userInfo.findDevice(deviceId)
         .map(device -> device.isInCooldownPeriod(cooldownPeriod))
         .orElseThrow(() -> new FailureException("Expected used Device ID to be known."));
   }

   private void publishDeviceInCooldownUsedEvent(UserId userId, Airlock2FAAccountId accountId, Airlock2FADeviceId airlock2FADeviceId) {
      ((Airlock2FADeviceEventFactory)this.eventFactory.get()).publishAirlock2FADeviceInCooldownUsed(userId, accountId, airlock2FADeviceId);
   }

   private AuthenticationStepResult handleDeny(Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException> currentStatus) {
      Airlock2FAAuthenticationService.AuthenticationStatusAnswer answer = currentStatus.orElseThrow(Function.identity());
      Result<Optional<UserIdentity>, AuthenticationStepResult> identificationResult = this.tryIdentifyUser(answer);
      if (identificationResult.isFailure()) {
         return identificationResult.getFailure();
      }

      answer.getStatus().ifPresent(((AuthenticationStatusService)this.authenticationStatusService.get())::handleStatus);
      return this.stepResultFactory().fail(AuthenticationFlowStep.GeneralAuthenticationStepError.AUTHENTICATION_FAILED).build();
   }

   private Result<Optional<UserIdentity>, AuthenticationStepResult> tryIdentifyUser(Airlock2FAAuthenticationService.AuthenticationStatusAnswer answer) {
      if (answer.getAccountId().isEmpty()) {
         return Result.result(Optional.empty());
      }

      Optional<Airlock2FAUser> airlock2FAUser = ((Airlock2FAUserRepository)this.airlock2FAUserRepositoryProvider.get()).findBy(answer.getAccountId().get());
      if (airlock2FAUser.isEmpty()) {
         return Result.failure(this.stepResultFactory().fail(UserIdentifyingStep.UserIdentifyingStepError.USER_NOT_FOUND).build());
      }

      IdentificationResult identificationResult = ((IdentityProvidingService)this.identityProvidingService.get())
         .tryIdentify(ProvidedUsername.providedUsernameOf(airlock2FAUser.get().getIamUserId().getValue()));
      return identificationResult.isFailure()
         ? Result.failure(
            IdentificationResultConverter.convertToAuthStepResult(identificationResult.getFailure().orElseThrow(), this.stepResultFactory()).build()
         )
         : Result.result(identificationResult.getUserIdentity());
   }

   private Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException> checkStatus(Airlock2FASessionId sessionId) {
      return ((Airlock2FAAuthenticationService)this.service.get()).checkAuthenticationStatusUsernameless(sessionId);
   }

   private void logStatusAnswer(Optional<Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException>> previousStatus) {
      previousStatus.ifPresent(this::logStatusAnswer);
   }

   private void logStatusAnswer(Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException> status) {
      status.peek().ifPresent(answer -> ((Airlock2FALogHelper)this.logHelper.get()).logAuthenticationAnswerDetails(answer, this.stepSession().getLogData()));
   }

   private boolean isRenewalDue() {
      return this.stepSession()
         .isRenewalDue(
            (Airlock2FAUsernamelessStepSettings)this.settings.get(),
            (TimeProvider)this.time.get(),
            ((Airlock2FAUsernamelessStepSettings)this.settings.get()).qrCodeValidity()
         );
   }

   private AuthenticationStepResult timeoutResult() {
      return ((Airlock2FAUsernamelessStepSettings)this.settings.get())
         .timeoutGoto()
         .map(
            stepId -> this.stepResultFactory()
               .failGoto(Airlock2FAUsernamelessQrCodeAuthenticationStep.Airlock2FAUsernamelessQrCodeStepErrors.AIRLOCK_2FA_USERNAMELESS_TIMEOUT, stepId)
               .isFailedFactor(false)
               .build()
         )
         .orElse(
            this.stepResultFactory()
               .fail(Airlock2FAUsernamelessQrCodeAuthenticationStep.Airlock2FAUsernamelessQrCodeStepErrors.AIRLOCK_2FA_USERNAMELESS_TIMEOUT)
               .isFailedFactor(false)
               .build()
         );
   }

   private static boolean isSuccess(Optional<Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException>> status) {
      return status.<boolean>map(Airlock2FAUsernamelessQrCodeAuthenticationStep::isSuccess).orElse(false);
   }

   private static boolean isSuccess(Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException> status) {
      return status.peek().map(s -> s.getResult() == Airlock2FAAuthenticationService.AuthenticationStatusResult.ALLOW).orElse(false);
   }

   private static boolean isFinalDeny(Optional<Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException>> currentStatus) {
      return currentStatus.<boolean>map(Airlock2FAUsernamelessQrCodeAuthenticationStep::isFinalDeny).orElse(false);
   }

   private static boolean isFinalDeny(Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException> currentStatus) {
      return currentStatus.<Boolean>mapResult(
            answer -> answer.getResult() == Airlock2FAAuthenticationService.AuthenticationStatusResult.DENY
               && answer.getStatus().filter(AuthenticationStatus.TIMEOUT_RETRY::equals).isEmpty()
         )
         .orMapFailure(e -> false);
   }

   private static boolean isTimeout(Result<Airlock2FAAuthenticationService.AuthenticationStatusAnswer, FuturaeErrorResponseException> status) {
      return status.mapResult(Airlock2FAAuthenticationService.AuthenticationStatusAnswer::isTimeoutRetry)
         .orMapFailure(error -> error.type() == FuturaeErrorResponseException.FuturaeErrorResponseType.BAD_REQUEST);
   }

   @Override
   public String getDescription() {
      return "Airlock 2FA Usernameless QR Code Authentication Step";
   }

   @Override
   public AuthenticationMethod getAuthenticationMethod() {
      return KnownAuthenticationMethods.AIRLOCK_2FA;
   }

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

   private void fetchQrCode() {
      DeferredAuthenticationAnswer.UsernamelessQrCodeDeferredAuthenticationAnswer answer = ((Airlock2FAAuthenticationService)this.service.get())
         .authenticateWithUsernamelessQrCode(((Airlock2FAUsernamelessStepSettings)this.settings.get()).qrCodeValidity(), this.generateMessage());
      this.stepSession().refreshQrCode((TimeProvider)this.time.get(), answer.getSessionId(), answer.getQrCodeImage());
      ((Airlock2FALogHelper)this.logHelper.get()).logSessionStarted(Airlock2FAFactor.USERNAMELESS_QR_CODE, this.stepSession().getLogData());
   }

   private Optional<Airlock2FAMessage> generateMessage() {
      return ((Optional)this.messageProvider.get())
         .map(
            provider -> {
               try {
                  return provider.generateAirlock2FAMessage();
               } catch (Airlock2FAMessageProviderException e) {
                  LOG.error(
                     EDefaultLogType.GENERIC_LOG_TYPE,
                     "Failed to generate Airlock 2FA message with error: "
                        + LogUtil.quote(e.getMessage())
                        + ". This could be caused by an error in the config of the message provider."
                  );
                  throw new FailureException(e);
               }
            }
         );
   }

   private AuthenticationStepResult pollingRequired(Airlock2FAUsernamelessQrCodeAuthenticationStepSession session) {
      return this.stepResultFactory()
         .stay(Airlock2FAUsernamelessQrCodeAuthenticationStep.Airlock2FAUsernamelessQrCodeStepActions.AIRLOCK_2FA_USERNAMELESS_POLLING_REQUIRED)
         .addAdditionalAttribute("challengeImage", session.getCurrentQrCode().orElseThrow().getEncodedImage())
         .build();
   }

   private void handleAuthenticationSuccess(
      Airlock2FAAuthenticationService.AuthenticationStatusAnswer answer, Airlock2FAAccountId accountId, Airlock2FAUserInformation userInfo
   ) {
      Airlock2FADeviceInformation device = deviceInfoOfUsedDevice(answer, userInfo);
      this.logSuccess(accountId, device, answer.getUserPresenceVerification());
      ((Airlock2FAAuthTokenIdService)this.authTokenIdService.get()).handleSuccessfulUsage(Airlock2FAFactor.USERNAMELESS_QR_CODE, userInfo, device.getId());
      Optional<Duration> cooldownPeriod = ((Airlock2FAUsernamelessStepSettings)this.settings.get()).deviceSettings().cooldownSettings().cooldownPeriod();
      Airlock2FADeviceId deviceId = device.getId();
      ((Airlock2FAFlowSessionAttributesService)this.attributes.get())
         .saveAuthDevice(
            deviceId,
            ((Airlock2FACooldownInformationService)this.cooldownService.get()).determineAuthDeviceCooldownStatus(accountId, deviceId, cooldownPeriod),
            ((Airlock2FACooldownInformationService)this.cooldownService.get()).determineAuthDeviceCooldownEnds(accountId, deviceId, cooldownPeriod),
            cooldownPeriod
         );
   }

   private static Airlock2FADeviceInformation deviceInfoOfUsedDevice(
      Airlock2FAAuthenticationService.AuthenticationStatusAnswer answer, Airlock2FAUserInformation userInfo
   ) {
      return userInfo.getDevices()
         .stream()
         .filter(d -> d.getId().equals(answer.getDeviceId().orElseThrow(() -> new FailureException("Expected Device ID in response with result=\"allow\""))))
         .findFirst()
         .orElseThrow();
   }

   private void logSuccess(
      Airlock2FAAccountId accountId, Airlock2FADeviceInformation device, Optional<Airlock2FAUserPresenceVerification> userPresenceVerification
   ) {
      ((Airlock2FALogHelper)this.logHelper.get())
         .logSuccess(
            Airlock2FAFactor.USERNAMELESS_QR_CODE,
            device.toAuthDevice(),
            "authentication",
            this.stepSession()
               .getLogData()
               .withAccountId(accountId)
               .withDeviceId(device.getId())
               .withUserPresenceVerificationIfPresent(userPresenceVerification)
         );
   }

   private Airlock2FAUserInformation fetchUserInfo(Airlock2FAAccountId accountId) {
      return ((Airlock2FAUserInformationService)this.airlock2FAUserInformationService.get())
         .getUser(accountId)
         .orElseThrow(() -> new FailureException("Expected Futurae to provide user information"));
   }

   private AuthenticationStepResult successResult(UserIdentity identity, Airlock2FADeviceId airlock2FADeviceId) {
      return this.stepResultFactory()
         .asAuthenticationStepResult(this.stepResultFactory().success().build())
         .internalResultData(new UserIdentificationResultData(identity))
         .deviceUsageData(this.deviceUsageData(airlock2FADeviceId))
         .build();
   }

   private AuthenticationDeviceUsage deviceUsageData(Airlock2FADeviceId device) {
      return new AuthenticationDeviceUsage(KnownDeviceTypes.AIRLOCK_2FA, device, ((TimeProvider)this.time.get()).getCurrentInstant());
   }

   private Airlock2FAUsernamelessQrCodeAuthenticationStepSession stepSession() {
      return (Airlock2FAUsernamelessQrCodeAuthenticationStepSession)((Optional)this.stepSession.get()).orElseThrow();
   }

   public enum Airlock2FAUsernamelessQrCodeStepActions implements StepAction {
      AIRLOCK_2FA_USERNAMELESS_POLLING_REQUIRED;
   }

   enum Airlock2FAUsernamelessQrCodeStepErrors implements StepError {
      AIRLOCK_2FA_USERNAMELESS_TIMEOUT;
   }
}
