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.MapifiedValuesBuilder;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthLogData;
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.Airlock2FAFactorSet;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAMobileAuthUri;
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.flow.api.domain.model.step.StepSession;
import com.airlock.iam.flow.shared.domain.model.mapification.Airlock2FARemainingFactorsAndDevicesMapifier;
import com.airlock.iam.flow.shared.domain.model.session.PollingResultCache;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import lombok.NonNull;

public final class Airlock2FAMobileOnlyAuthenticationStepSession implements StepSession {
   private static final String ACCOUNTID_KEY = "accountId";
   private static final String FUTURAE_SESSION_KEY = "futuraeSession";
   private static final String URI_KEY = "uri";
   private static final String REMAINING_FACTORS_AND_DEVICES_KEY = "remainingFactorsAndDevices";
   private static final String ALLOWED_FACTORS_KEY = "allowedFactors";
   private static final String DEVICE_ID_KEY = "deviceId";
   private static final String PREAUTH_DONE_KEY = "preauthDone";
   private final PollingResultCache pollingCache;
   private Optional<Airlock2FAAccountId> accountId = Optional.empty();
   private Optional<Airlock2FAFuturaeAuthSession> futuraeAuthSession = Optional.empty();
   private Optional<Airlock2FAMobileAuthUri> approvalUrl = Optional.empty();
   private Airlock2FARemainingFactorsAndDevices remainingFactorsAndDevices = Airlock2FARemainingFactorsAndDevices.remainingFactorsAndDevices();
   private Airlock2FAFactorSet allowedFactors = Airlock2FAFactorSet.of();
   private Optional<Airlock2FADeviceId> deviceId = Optional.empty();
   private boolean preauthDone;

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

   public void setAccountId(@NonNull Airlock2FAAccountId accountId) {
      if (accountId == null) {
         throw new NullPointerException("accountId is marked non-null but is null");
      }

      this.accountId = Optional.of(accountId);
   }

   public void setFuturaeSession(@NonNull Airlock2FASessionId sessionId) {
      if (sessionId == null) {
         throw new NullPointerException("sessionId is marked non-null but is null");
      }

      Preconditions.checkState(this.accountId.isPresent(), "Airlock 2FA account ID expected to be set at this point");
      this.futuraeAuthSession = Optional.of(new Airlock2FAFuturaeAuthSession(this.accountId.get(), sessionId, Airlock2FAFactor.MOBILE_ONLY));
   }

   public void setApprovalUrl(@NonNull Airlock2FAMobileAuthUri approvalUrl) {
      if (approvalUrl == null) {
         throw new NullPointerException("approvalUrl is marked non-null but is null");
      }

      this.approvalUrl = Optional.of(approvalUrl);
   }

   public void setDeviceId(@NonNull Airlock2FADeviceId deviceId) {
      if (deviceId == null) {
         throw new NullPointerException("deviceId is marked non-null but is null");
      }

      this.deviceId = Optional.of(deviceId);
   }

   public void setPreAuthDoneWithBypass() {
      this.preauthDone = true;
   }

   public void setPreAuthInformation(
      @NonNull Airlock2FAFactorSet factors, @NonNull Airlock2FADeviceSettings deviceSettings, @NonNull Airlock2FADevices allowedDevices
   ) {
      if (factors == null) {
         throw new NullPointerException("factors is marked non-null but is null");
      }

      if (deviceSettings == null) {
         throw new NullPointerException("deviceSettings is marked non-null but is null");
      }

      if (allowedDevices == null) {
         throw new NullPointerException("allowedDevices is marked non-null but is null");
      }

      this.allowedFactors = factors;
      this.remainingFactorsAndDevices = Airlock2FARemainingFactorsAndDevices.remainingFactorsAndDevices(deviceSettings, allowedDevices);
      if (!this.remainingDevices().isEmpty()) {
         this.preauthDone = true;
      }
   }

   public Airlock2FADeviceId getDeviceId() {
      return this.deviceId.orElseThrow(() -> new FailureException("Device ID not set"));
   }

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

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

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

   @Override
   public MapifiedValues mapify() {
      return MapifiedValuesBuilder.mapifiedValues()
         .putIfPresent("pollingCache", this.pollingCache.mapify())
         .putIfPresent("accountId", this.accountId.map(Airlock2FAAccountId::getId))
         .putIfPresent("futuraeSession", this.futuraeAuthSession.map(Airlock2FAFuturaeAuthSessionMapifier::mapifyAirlock2FAFuturaeAuthSession))
         .putIfPresent("uri", this.approvalUrl.map(Airlock2FAMobileAuthUri::uri))
         .put("remainingFactorsAndDevices", Airlock2FARemainingFactorsAndDevicesMapifier.mapify(this.remainingFactorsAndDevices))
         .putIfNotEmpty("allowedFactors", this.allowedFactors.stream().map(Enum::name).toList())
         .putIfPresent("deviceId", this.deviceId.map(Airlock2FADeviceId::getId))
         .put("preauthDone", this.preauthDone)
         .build();
   }

   public static Airlock2FAMobileOnlyAuthenticationStepSession demapify(MapifiedValues values) {
      return new Airlock2FAMobileOnlyAuthenticationStepSession(
         PollingResultCache.demapifyPollingCache(values.getOptionalValues("pollingCache")),
         values.transformOptional("accountId", id -> Airlock2FAAccountId.airlock2FAAccountId((String)id)),
         values.demapifyOptional("futuraeSession", Airlock2FAFuturaeAuthSessionMapifier::demapifyAirlock2FAFuturaeAuthSession),
         values.transformOptional("uri", v -> new Airlock2FAMobileAuthUri((String)v)),
         values.demapifyValue("remainingFactorsAndDevices", Airlock2FARemainingFactorsAndDevicesMapifier::demapify),
         Airlock2FAFactorSet.of(values.<String>getList("allowedFactors").stream().map(Airlock2FAFactor::valueOf).toList()),
         values.transformOptional("deviceId", v -> Airlock2FADeviceId.airlock2FADeviceId((String)v)),
         values.<Boolean>get("preauthDone")
      );
   }

   public Airlock2FAAuthLogData logData() {
      return Airlock2FAAuthLogData.airlock2FAAuthLogData(this.accountId.orElseThrow())
         .withDeviceIdIfPresent(this.deviceId)
         .withSessionIdIfPresent(this.futuraeAuthSession.map(Airlock2FAFuturaeAuthSession::getSessionId));
   }

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

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

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

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FAMobileAuthUri> getApprovalUrl() {
      return this.approvalUrl;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public boolean isPreauthDone() {
      return this.preauthDone;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   private Airlock2FAMobileOnlyAuthenticationStepSession(
      PollingResultCache pollingCache,
      Optional<Airlock2FAAccountId> accountId,
      Optional<Airlock2FAFuturaeAuthSession> futuraeAuthSession,
      Optional<Airlock2FAMobileAuthUri> approvalUrl,
      Airlock2FARemainingFactorsAndDevices remainingFactorsAndDevices,
      Airlock2FAFactorSet allowedFactors,
      Optional<Airlock2FADeviceId> deviceId,
      boolean preauthDone
   ) {
      this.pollingCache = pollingCache;
      this.accountId = accountId;
      this.futuraeAuthSession = futuraeAuthSession;
      this.approvalUrl = approvalUrl;
      this.remainingFactorsAndDevices = remainingFactorsAndDevices;
      this.allowedFactors = allowedFactors;
      this.deviceId = deviceId;
      this.preauthDone = preauthDone;
   }
}
