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.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.mapification.MapifiedValues;
import com.airlock.iam.base.domain.model.mapification.MapificationUtils;
import com.airlock.iam.base.domain.model.mapification.MapifiedValuesBuilder;
import com.airlock.iam.common.api.domain.model.authentication.factor.AuthenticationFactorDetail;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactorDetail;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.elog.api.LogTypes;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthDevice;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthDeviceMapifier;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthLogData;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FADeviceCapability;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FADeviceSettings;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FADevices;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactor;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactorPriority;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactorSet;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAMultiNumberedChallenge;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAPayloadEncryptionSettings;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FARemainingFactorsAndDevices;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FASessionId;
import com.airlock.iam.factor.domain.model.airlock2fa.futurae.Airlock2FAFuturaeAuthSession;
import com.airlock.iam.factor.domain.model.airlock2fa.futurae.Airlock2FAFuturaeAuthSessionMapifier;
import com.airlock.iam.factor.domain.model.airlock2fa.qrcode.Airlock2FAExchangeTokenData;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAFactorInfoMapper;
import com.airlock.iam.flow.api.domain.model.step.StepSession;
import com.airlock.iam.flow.shared.domain.model.airlock2fa.Airlock2FAOfflineQrCodeChallenge;
import com.airlock.iam.flow.shared.domain.model.airlock2fa.Airlock2FAOnlineQrCodeChallenge;
import com.airlock.iam.flow.shared.domain.model.mapification.Airlock2FARemainingFactorsAndDevicesMapifier;
import com.airlock.iam.flow.shared.domain.model.session.PollingResultCache;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

public final class Airlock2FAAuthenticationStepSession implements StepSession {
   private static final ELogger LOG = ELoggerFactory.getLogger(Airlock2FAAuthenticationStepSession.class);
   private static final List<Airlock2FAFactor> SUPPORTED_FACTORS = List.of(
      Airlock2FAFactor.ONLINE_QR_CODE, Airlock2FAFactor.ONE_TOUCH, Airlock2FAFactor.OFFLINE_QR_CODE, Airlock2FAFactor.PASSCODE
   );
   private static final String REMAINING_PASSCODE_CHECK_ATTEMPTS_KEY = "remainingAttempts";
   private static final String FUTURAE_SESSION_KEY = "futuraeSession";
   private static final String ACCOUNT_ID_KEY = "accountId";
   private static final String ALLOWED_FACTORS_KEY = "allowedFactors";
   private static final String ALLOWED_DEVICES_KEY = "allowedDevices";
   private static final String REMAINING_FACTORS_AND_DEVICES_KEY = "remainingFactorsAndDevices";
   private static final String OFFLINE_QR_CODE_CHALLENGE_KEY = "qrCode";
   private static final String ONLINE_QR_CODE_CHALLENGE_KEY = "onlineQrCode";
   private static final String SESSION_EXCHANGE_TOKENS_KEY = "sessionExchangeTokens";
   private static final String SESSION_START_TIME_KEY = "sessionStartTime";
   private static final String AUTHENTICATION_FACTOR_KEY = "factor";
   private static final String BYPASSED_FACTOR_KEY = "bypassed";
   private static final String LOGIN_ID_KEY = "loginId";
   private static final String MULTI_NUMBERED_CHALLENGE_KEY = "multiNumberedChallenge";
   private final PollingResultCache pollingCache;
   private int remainingPasscodeCheckAttempts;
   private Optional<Airlock2FAFuturaeAuthSession> futuraeSession = Optional.empty();
   private Optional<Airlock2FAAccountId> accountId = Optional.empty();
   private Airlock2FAFactorSet factorsAllowedByBackend = Airlock2FAFactorSet.of();
   private Airlock2FADevices allowedDevices = Airlock2FADevices.of();
   private Airlock2FARemainingFactorsAndDevices remainingFactorsAndDevices = Airlock2FARemainingFactorsAndDevices.remainingFactorsAndDevices();
   private Optional<Airlock2FAOfflineQrCodeChallenge> offlineQrCodeChallenge = Optional.empty();
   private Optional<Airlock2FAOnlineQrCodeChallenge> onlineQrCodeChallenge = Optional.empty();
   private List<Airlock2FAExchangeTokenData> sessionExchangeTokens = Collections.emptyList();
   private Optional<Instant> sessionStartTime = Optional.empty();
   private Optional<Airlock2FAFactor> factorUsedForAuthentication = Optional.empty();
   private Optional<Airlock2FAFactor> bypasedFactor = Optional.empty();
   private Optional<String> loginId = Optional.empty();
   private Optional<Airlock2FAMultiNumberedChallenge> multiNumberedChallenge = Optional.empty();

   public void initializeFactorsAndDevices(Airlock2FADeviceSettings deviceSettings, Airlock2FAFactorSet factorsFromBackend, Airlock2FADevices devices) {
      this.factorsAllowedByBackend = factorsFromBackend;
      Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings = deviceSettings.payloadEncryptionSettings();
      if (deviceSettings.factorPriority().contains(Airlock2FAFactor.OFFLINE_QR_CODE) && payloadEncryptionSettings.requireEncryptedPayloadForHwTokens()) {
         this.logWarningIfUserHasQrCodeScanningHwToken(devices);
      }

      this.remainingFactorsAndDevices = Airlock2FARemainingFactorsAndDevices.remainingFactorsAndDevices(deviceSettings, devices);
      this.remainingFactorsAndDevices.intersectFactors(factorsFromBackend.factors(), payloadEncryptionSettings);
      this.allowedDevices = this.remainingFactorsAndDevices.remainingDevices();
   }

   private void logWarningIfUserHasQrCodeScanningHwToken(Airlock2FADevices devices) {
      devices.stream()
         .filter(device -> device.isHardwareToken() && device.getCapabilities().contains(Airlock2FADeviceCapability.QR_CODE))
         .forEach(
            device -> LOG.warn(
               LogTypes.CONFIGURATION_ERROR,
               "Factor %s is not allowed for hardware device with id \"%s\" because payload encryption is enabled and unencrypted payload for HW tokens is disabled."
                  .formatted(Airlock2FAFactor.OFFLINE_QR_CODE.getDisplayName(), device.getId())
            )
         );
   }

   public void setBypassedFactor(Airlock2FAFactor factor) {
      this.bypasedFactor = Optional.of(factor);
   }

   public static Airlock2FAAuthenticationStepSession emptySession() {
      return new Airlock2FAAuthenticationStepSession(new PollingResultCache());
   }

   public Airlock2FAAuthenticationStepSession initialize(Airlock2FAAuthenticationStepSettings stepSettings) {
      if (stepSettings.getDeviceSettings().factorPriority().contains(Airlock2FAFactor.PASSCODE)) {
         this.remainingPasscodeCheckAttempts = stepSettings.getMaxFailedPasscodeCheckAttempts() + 1;
      }

      return this;
   }

   public Airlock2FAFactorPriority getRemainingFactors() {
      return this.remainingFactorsAndDevices.factors();
   }

   public Optional<Airlock2FAFactor> topFactor() {
      return this.remainingFactorsAndDevices.topFactor();
   }

   public boolean topFactorEquals(Airlock2FAFactor factor) {
      return this.topFactor().map(factor::equals).orElse(false);
   }

   public boolean requiresDeviceSelection(boolean pushToAllEnabled) {
      return this.remainingFactorsAndDevices.requiresDeviceSelection(pushToAllEnabled);
   }

   public void setAccountId(Airlock2FAAccountId accountId) {
      this.accountId = Optional.of(accountId);
   }

   public Airlock2FAAccountId getAccountIdOrThrow() {
      return this.accountId.orElseThrow();
   }

   public Airlock2FADeviceId getOnlyDeviceIdOrThrow() {
      return this.getOnlyDeviceOrThrow().getId();
   }

   public Airlock2FAAuthDevice getOnlyDeviceOrThrow() {
      return this.remainingDevices().onlyDeviceOrThrow();
   }

   public AuthenticationFactorDetail authenticationFactorDetail() {
      return this.bypasedFactor
         .or(() -> this.factorUsedForAuthentication)
         .map(Airlock2FAFactorInfoMapper::authenticationFactorDetailForAirlock2FAFactor)
         .orElse(KnownAuthenticationFactorDetail.PREAUTH_AIRLOCK_2FA);
   }

   public void setOfflineQrCodeChallenge(Airlock2FAOfflineQrCodeChallenge offlineQrCodeChallenge) {
      this.offlineQrCodeChallenge = Optional.of(offlineQrCodeChallenge);
   }

   public void setOnlineQrCodeChallenge(Airlock2FAOnlineQrCodeChallenge onlineQrCodeChallenge) {
      this.onlineQrCodeChallenge = Optional.of(onlineQrCodeChallenge);
   }

   public void setSessionExchangeTokens(List<Airlock2FAExchangeTokenData> sessionExchangeTokens) {
      this.sessionExchangeTokens = sessionExchangeTokens;
   }

   public void setSessionStartTime(Instant startTime) {
      this.sessionStartTime = Optional.of(startTime);
   }

   public void switchToOfflineMode(Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings) {
      FailureException.checkTrue(this.canEnableOfflineMode(), "Must be possible to enable offline mode.");
      this.onlineQrCodeChallenge = Optional.empty();
      this.factorUsedForAuthentication = Optional.empty();
      this.remainingFactorsAndDevices.switchToOffline(payloadEncryptionSettings);
   }

   public boolean isInOfflineMode() {
      return !this.remainingFactorsAndDevices.factors().containsOnlineFactor();
   }

   public boolean canStartOnlineSession() {
      return this.remainingFactorsAndDevices.factors().containsOnlineFactor();
   }

   public boolean canEnableOfflineMode() {
      return this.remainingFactorsAndDevices.factors().containsOfflineFactor();
   }

   public void reduceRemainingPasscodeCheckAttempts() {
      FailureException.checkTrue(
         this.topFactor().isPresent() && this.topFactor().get() == Airlock2FAFactor.PASSCODE, "Highest priority factor must be Passcode."
      );
      this.remainingPasscodeCheckAttempts--;
   }

   public void setFuturaeSession(Airlock2FASessionId sessionId, Airlock2FAFactor factor) {
      FailureException.checkTrue(this.accountId.isPresent(), "Airlock 2FA account ID expected to be set at this point");
      FailureException.checkTrue(this.factorUsedForAuthentication.isPresent(), "Airlock 2FA authentication expected to have started at this point");
      this.futuraeSession = Optional.of(new Airlock2FAFuturaeAuthSession(this.accountId.orElseThrow(), sessionId, factor));
   }

   public void clearFuturaeSession() {
      this.futuraeSession = Optional.empty();
   }

   public void setFactorUsedForAuthentication(Airlock2FAFactor factor) {
      this.checkFactorAllowed(factor);
      this.factorUsedForAuthentication = Optional.of(factor);
   }

   public void setExclusiveFactor(Airlock2FAFactor factor, Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings) {
      this.checkFactorAllowed(factor);
      this.remainingFactorsAndDevices.intersectFactors(List.of(factor), payloadEncryptionSettings);
   }

   private void checkFactorAllowed(Airlock2FAFactor factor) {
      FailureException.checkTrue(SUPPORTED_FACTORS.contains(factor), "Factor must be supported by step.");
      FailureException.checkTrue(this.getRemainingFactors().contains(factor), "Factor must be contained in remaining factors.");
   }

   public Airlock2FADevices remainingDevices() {
      return this.remainingFactorsAndDevices.remainingDevices();
   }

   public Airlock2FADevices cooldownDevices() {
      return this.remainingFactorsAndDevices.devicesInCooldown();
   }

   public void selectDevice(Airlock2FAAuthDevice device, Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings) {
      this.remainingFactorsAndDevices.selectDevice(device, payloadEncryptionSettings);
   }

   public Airlock2FAFactorSet getFactorsAllowedByFuturaeAndWithCapableDevices(Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings) {
      return this.factorsAllowedByBackend.intersect(this.factorsWithCapableDevices(payloadEncryptionSettings));
   }

   private Airlock2FAFactorSet factorsWithCapableDevices(Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings) {
      return Airlock2FAFactorSet.of(this.remainingDevices().stream().flatMap(d -> d.getSupportedFactors(payloadEncryptionSettings).stream()).toList());
   }

   private Airlock2FAAuthDevice resolveDevice(Airlock2FADeviceId id) {
      return this.allowedDevices.deviceWithId(id).orElseThrow(() -> new FailureException("No device with id."));
   }

   public Airlock2FAAuthLogData getLogData() {
      return Airlock2FAAuthLogData.airlock2FAAuthLogData(this.accountId.orElseThrow())
         .withDeviceId(this.remainingDevices().hasOneDevice() ? this.getOnlyDeviceIdOrThrow() : null)
         .withSessionIdIfPresent(this.getFuturaeSession().map(Airlock2FAFuturaeAuthSession::getSessionId));
   }

   public void selectDeviceById(Airlock2FADeviceId deviceId, Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings) {
      this.selectDevice(this.resolveDevice(deviceId), payloadEncryptionSettings);
   }

   public void setLoginId(String loginId) {
      this.loginId = Optional.of(loginId);
   }

   public boolean isDeviceInCooldown(Airlock2FADeviceId deviceId) {
      return this.cooldownDevices().containsDeviceWithId(deviceId);
   }

   public boolean isDeviceAllowed(Airlock2FADeviceId deviceId) {
      return this.allowedDevices.containsDeviceWithId(deviceId);
   }

   @Override
   public MapifiedValues mapify() {
      return MapifiedValuesBuilder.mapifiedValues()
         .put("remainingAttempts", this.remainingPasscodeCheckAttempts)
         .putIfPresent("futuraeSession", this.futuraeSession.map(Airlock2FAFuturaeAuthSessionMapifier::mapifyAirlock2FAFuturaeAuthSession))
         .putIfPresent("accountId", this.accountId.map(Airlock2FAAccountId::getId))
         .putIfNotEmpty("allowedFactors", this.factorsAllowedByBackend.stream().map(Enum::name).toList())
         .putIfNotEmpty("allowedDevices", this.allowedDevices.stream().map(Airlock2FAAuthDeviceMapifier::mapifyAirlock2FAAuthDevice).toList())
         .put("remainingFactorsAndDevices", Airlock2FARemainingFactorsAndDevicesMapifier.mapify(this.remainingFactorsAndDevices))
         .putIfPresent("qrCode", this.offlineQrCodeChallenge.map(Airlock2FAOfflineQrCodeChallenge::mapify))
         .putIfPresent("onlineQrCode", this.onlineQrCodeChallenge.map(Airlock2FAOnlineQrCodeChallenge::mapify))
         .putIfNotEmpty("sessionExchangeTokens", MapificationUtils.mapifyCollection(this.sessionExchangeTokens, Airlock2FAExchangeTokenData::mapify))
         .putIfPresent("sessionStartTime", this.sessionStartTime.map(MapificationUtils::mapifyInstant))
         .putIfPresent("factor", this.factorUsedForAuthentication.map(Enum::name))
         .putIfPresent("bypassed", this.bypasedFactor.map(Enum::name))
         .putIfPresent("loginId", this.loginId)
         .putIfPresent("pollingCache", this.pollingCache.mapify())
         .putIfPresent("multiNumberedChallenge", this.multiNumberedChallenge.map(Airlock2FAMultiNumberedChallenge::value))
         .build();
   }

   public static Airlock2FAAuthenticationStepSession demapify(MapifiedValues values) {
      return new Airlock2FAAuthenticationStepSession(
         PollingResultCache.demapifyPollingCache(values.getOptionalValues("pollingCache")),
         values.<Integer>get("remainingAttempts"),
         values.demapifyOptional("futuraeSession", Airlock2FAFuturaeAuthSessionMapifier::demapifyAirlock2FAFuturaeAuthSession),
         values.transformOptional("accountId", id -> Airlock2FAAccountId.airlock2FAAccountId((String)id)),
         Airlock2FAFactorSet.of(values.<String>getList("allowedFactors").stream().map(Airlock2FAFactor::valueOf).toList()),
         Airlock2FADevices.of(values.demapifyList("allowedDevices", Airlock2FAAuthDeviceMapifier::demapifyAirlock2FAAuthDevice)),
         values.demapifyValue("remainingFactorsAndDevices", Airlock2FARemainingFactorsAndDevicesMapifier::demapify),
         values.demapifyOptional("qrCode", Airlock2FAOfflineQrCodeChallenge::demapify),
         values.demapifyOptional("onlineQrCode", Airlock2FAOnlineQrCodeChallenge::demapify),
         values.demapifyList("sessionExchangeTokens", Airlock2FAExchangeTokenData::demapify),
         values.demapifyOptional("sessionStartTime", MapificationUtils::demapifyInstant),
         values.<String>getOptional("factor").map(Airlock2FAFactor::valueOf),
         values.<String>getOptional("bypassed").map(Airlock2FAFactor::valueOf),
         values.getOptional("loginId"),
         values.<Integer>getOptional("multiNumberedChallenge").map(Airlock2FAMultiNumberedChallenge::new)
      );
   }

   @SuppressFBWarnings(justification = "generated code")
   public PollingResultCache getPollingCache() {
      return this.pollingCache;
   }

   @SuppressFBWarnings(justification = "generated code")
   public int getRemainingPasscodeCheckAttempts() {
      return this.remainingPasscodeCheckAttempts;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FAFuturaeAuthSession> getFuturaeSession() {
      return this.futuraeSession;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FAAccountId> getAccountId() {
      return this.accountId;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Airlock2FAFactorSet getFactorsAllowedByBackend() {
      return this.factorsAllowedByBackend;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Airlock2FADevices getAllowedDevices() {
      return this.allowedDevices;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Airlock2FARemainingFactorsAndDevices getRemainingFactorsAndDevices() {
      return this.remainingFactorsAndDevices;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FAOfflineQrCodeChallenge> getOfflineQrCodeChallenge() {
      return this.offlineQrCodeChallenge;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FAOnlineQrCodeChallenge> getOnlineQrCodeChallenge() {
      return this.onlineQrCodeChallenge;
   }

   @SuppressFBWarnings(justification = "generated code")
   public List<Airlock2FAExchangeTokenData> getSessionExchangeTokens() {
      return this.sessionExchangeTokens;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Instant> getSessionStartTime() {
      return this.sessionStartTime;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FAFactor> getFactorUsedForAuthentication() {
      return this.factorUsedForAuthentication;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FAFactor> getBypasedFactor() {
      return this.bypasedFactor;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<String> getLoginId() {
      return this.loginId;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FAMultiNumberedChallenge> getMultiNumberedChallenge() {
      return this.multiNumberedChallenge;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public boolean equals(Object o) {
      if (o == this) {
         return true;
      } else if (!(o instanceof Airlock2FAAuthenticationStepSession other)) {
         return false;
      } else {
         if (this.remainingPasscodeCheckAttempts != other.remainingPasscodeCheckAttempts) {
            return false;
         }

         Object this$pollingCache = this.pollingCache;
         Object other$pollingCache = other.pollingCache;
         if (this$pollingCache == null ? other$pollingCache == null : this$pollingCache.equals(other$pollingCache)) {
            Object this$futuraeSession = this.futuraeSession;
            Object other$futuraeSession = other.futuraeSession;
            if (this$futuraeSession == null ? other$futuraeSession == null : this$futuraeSession.equals(other$futuraeSession)) {
               Object this$accountId = this.accountId;
               Object other$accountId = other.accountId;
               if (this$accountId == null ? other$accountId == null : this$accountId.equals(other$accountId)) {
                  Object this$factorsAllowedByBackend = this.factorsAllowedByBackend;
                  Object other$factorsAllowedByBackend = other.factorsAllowedByBackend;
                  if (this$factorsAllowedByBackend == null
                     ? other$factorsAllowedByBackend == null
                     : this$factorsAllowedByBackend.equals(other$factorsAllowedByBackend)) {
                     Object this$allowedDevices = this.allowedDevices;
                     Object other$allowedDevices = other.allowedDevices;
                     if (this$allowedDevices == null ? other$allowedDevices == null : this$allowedDevices.equals(other$allowedDevices)) {
                        Object this$remainingFactorsAndDevices = this.remainingFactorsAndDevices;
                        Object other$remainingFactorsAndDevices = other.remainingFactorsAndDevices;
                        if (this$remainingFactorsAndDevices == null
                           ? other$remainingFactorsAndDevices == null
                           : this$remainingFactorsAndDevices.equals(other$remainingFactorsAndDevices)) {
                           Object this$offlineQrCodeChallenge = this.offlineQrCodeChallenge;
                           Object other$offlineQrCodeChallenge = other.offlineQrCodeChallenge;
                           if (this$offlineQrCodeChallenge == null
                              ? other$offlineQrCodeChallenge == null
                              : this$offlineQrCodeChallenge.equals(other$offlineQrCodeChallenge)) {
                              Object this$onlineQrCodeChallenge = this.onlineQrCodeChallenge;
                              Object other$onlineQrCodeChallenge = other.onlineQrCodeChallenge;
                              if (this$onlineQrCodeChallenge == null
                                 ? other$onlineQrCodeChallenge == null
                                 : this$onlineQrCodeChallenge.equals(other$onlineQrCodeChallenge)) {
                                 Object this$sessionExchangeTokens = this.sessionExchangeTokens;
                                 Object other$sessionExchangeTokens = other.sessionExchangeTokens;
                                 if (this$sessionExchangeTokens == null
                                    ? other$sessionExchangeTokens == null
                                    : this$sessionExchangeTokens.equals(other$sessionExchangeTokens)) {
                                    Object this$sessionStartTime = this.sessionStartTime;
                                    Object other$sessionStartTime = other.sessionStartTime;
                                    if (this$sessionStartTime == null ? other$sessionStartTime == null : this$sessionStartTime.equals(other$sessionStartTime)) {
                                       Object this$factorUsedForAuthentication = this.factorUsedForAuthentication;
                                       Object other$factorUsedForAuthentication = other.factorUsedForAuthentication;
                                       if (this$factorUsedForAuthentication == null
                                          ? other$factorUsedForAuthentication == null
                                          : this$factorUsedForAuthentication.equals(other$factorUsedForAuthentication)) {
                                          Object this$bypasedFactor = this.bypasedFactor;
                                          Object other$bypasedFactor = other.bypasedFactor;
                                          if (this$bypasedFactor == null ? other$bypasedFactor == null : this$bypasedFactor.equals(other$bypasedFactor)) {
                                             Object this$loginId = this.loginId;
                                             Object other$loginId = other.loginId;
                                             if (this$loginId == null ? other$loginId == null : this$loginId.equals(other$loginId)) {
                                                Object this$multiNumberedChallenge = this.multiNumberedChallenge;
                                                Object other$multiNumberedChallenge = other.multiNumberedChallenge;
                                                return this$multiNumberedChallenge == null
                                                   ? other$multiNumberedChallenge == null
                                                   : this$multiNumberedChallenge.equals(other$multiNumberedChallenge);
                                             } else {
                                                return false;
                                             }
                                          } else {
                                             return false;
                                          }
                                       } else {
                                          return false;
                                       }
                                    } else {
                                       return false;
                                    }
                                 } else {
                                    return false;
                                 }
                              } else {
                                 return false;
                              }
                           } else {
                              return false;
                           }
                        } else {
                           return false;
                        }
                     } else {
                        return false;
                     }
                  } else {
                     return false;
                  }
               } else {
                  return false;
               }
            } else {
               return false;
            }
         } else {
            return false;
         }
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public int hashCode() {
      int PRIME = 59;
      int result = 1;
      result = result * 59 + this.remainingPasscodeCheckAttempts;
      Object $pollingCache = this.pollingCache;
      result = result * 59 + ($pollingCache == null ? 43 : $pollingCache.hashCode());
      Object $futuraeSession = this.futuraeSession;
      result = result * 59 + ($futuraeSession == null ? 43 : $futuraeSession.hashCode());
      Object $accountId = this.accountId;
      result = result * 59 + ($accountId == null ? 43 : $accountId.hashCode());
      Object $factorsAllowedByBackend = this.factorsAllowedByBackend;
      result = result * 59 + ($factorsAllowedByBackend == null ? 43 : $factorsAllowedByBackend.hashCode());
      Object $allowedDevices = this.allowedDevices;
      result = result * 59 + ($allowedDevices == null ? 43 : $allowedDevices.hashCode());
      Object $remainingFactorsAndDevices = this.remainingFactorsAndDevices;
      result = result * 59 + ($remainingFactorsAndDevices == null ? 43 : $remainingFactorsAndDevices.hashCode());
      Object $offlineQrCodeChallenge = this.offlineQrCodeChallenge;
      result = result * 59 + ($offlineQrCodeChallenge == null ? 43 : $offlineQrCodeChallenge.hashCode());
      Object $onlineQrCodeChallenge = this.onlineQrCodeChallenge;
      result = result * 59 + ($onlineQrCodeChallenge == null ? 43 : $onlineQrCodeChallenge.hashCode());
      Object $sessionExchangeTokens = this.sessionExchangeTokens;
      result = result * 59 + ($sessionExchangeTokens == null ? 43 : $sessionExchangeTokens.hashCode());
      Object $sessionStartTime = this.sessionStartTime;
      result = result * 59 + ($sessionStartTime == null ? 43 : $sessionStartTime.hashCode());
      Object $factorUsedForAuthentication = this.factorUsedForAuthentication;
      result = result * 59 + ($factorUsedForAuthentication == null ? 43 : $factorUsedForAuthentication.hashCode());
      Object $bypasedFactor = this.bypasedFactor;
      result = result * 59 + ($bypasedFactor == null ? 43 : $bypasedFactor.hashCode());
      Object $loginId = this.loginId;
      result = result * 59 + ($loginId == null ? 43 : $loginId.hashCode());
      Object $multiNumberedChallenge = this.multiNumberedChallenge;
      return result * 59 + ($multiNumberedChallenge == null ? 43 : $multiNumberedChallenge.hashCode());
   }

   @SuppressFBWarnings(justification = "generated code")
   private Airlock2FAAuthenticationStepSession(PollingResultCache pollingCache) {
      this.pollingCache = pollingCache;
   }

   @SuppressFBWarnings(justification = "generated code")
   Airlock2FAAuthenticationStepSession(
      PollingResultCache pollingCache,
      int remainingPasscodeCheckAttempts,
      Optional<Airlock2FAFuturaeAuthSession> futuraeSession,
      Optional<Airlock2FAAccountId> accountId,
      Airlock2FAFactorSet factorsAllowedByBackend,
      Airlock2FADevices allowedDevices,
      Airlock2FARemainingFactorsAndDevices remainingFactorsAndDevices,
      Optional<Airlock2FAOfflineQrCodeChallenge> offlineQrCodeChallenge,
      Optional<Airlock2FAOnlineQrCodeChallenge> onlineQrCodeChallenge,
      List<Airlock2FAExchangeTokenData> sessionExchangeTokens,
      Optional<Instant> sessionStartTime,
      Optional<Airlock2FAFactor> factorUsedForAuthentication,
      Optional<Airlock2FAFactor> bypasedFactor,
      Optional<String> loginId,
      Optional<Airlock2FAMultiNumberedChallenge> multiNumberedChallenge
   ) {
      this.pollingCache = pollingCache;
      this.remainingPasscodeCheckAttempts = remainingPasscodeCheckAttempts;
      this.futuraeSession = futuraeSession;
      this.accountId = accountId;
      this.factorsAllowedByBackend = factorsAllowedByBackend;
      this.allowedDevices = allowedDevices;
      this.remainingFactorsAndDevices = remainingFactorsAndDevices;
      this.offlineQrCodeChallenge = offlineQrCodeChallenge;
      this.onlineQrCodeChallenge = onlineQrCodeChallenge;
      this.sessionExchangeTokens = sessionExchangeTokens;
      this.sessionStartTime = sessionStartTime;
      this.factorUsedForAuthentication = factorUsedForAuthentication;
      this.bypasedFactor = bypasedFactor;
      this.loginId = loginId;
      this.multiNumberedChallenge = multiNumberedChallenge;
   }

   @SuppressFBWarnings(justification = "generated code")
   public void setFactorsAllowedByBackend(Airlock2FAFactorSet factorsAllowedByBackend) {
      this.factorsAllowedByBackend = factorsAllowedByBackend;
   }

   @SuppressFBWarnings(justification = "generated code")
   public void setMultiNumberedChallenge(Optional<Airlock2FAMultiNumberedChallenge> multiNumberedChallenge) {
      this.multiNumberedChallenge = multiNumberedChallenge;
   }
}
