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

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.base.misc.time.TimeProvider;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthLogData;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FASessionId;
import com.airlock.iam.factor.domain.model.airlock2fa.qrcode.Airlock2FAQrCodeImage;
import com.airlock.iam.flow.api.domain.model.step.StepSession;
import com.airlock.iam.flow.shared.domain.model.session.PollingResultCache;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;

public class Airlock2FAUsernamelessQrCodeAuthenticationStepSession implements StepSession {
   private static final String CURRENT_SESSION_ID_KEY = "currentSessionId";
   private static final String PREVIOUS_SESSION_ID_KEY = "previousSessionId";
   private static final String QR_CODE_KEY = "qrCode";
   private static final String NUMBER_OF_QR_CODES_KEY = "numQrCode";
   private static final String CURRENT_QR_CODE_FETCHED_AT = "currentQrCodeFetchedAt";
   private final PollingResultCache pollingCache;
   private Optional<Airlock2FASessionId> currentSessionId = Optional.empty();
   private Optional<Airlock2FASessionId> previousSessionId = Optional.empty();
   private Optional<Airlock2FAQrCodeImage> currentQrCode = Optional.empty();
   private int numberOfQrCodes;
   private Optional<Instant> currentQrCodeFetchedAt = Optional.empty();

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

   public boolean isRenewalDue(Airlock2FAUsernamelessStepSettings settings, TimeProvider time, Duration qrCodeValidity) {
      return !this.maxRenewalsReached(settings.maxRenewals()) && this.isTimeToRenew(time, qrCodeValidity);
   }

   private boolean maxRenewalsReached(int maxRenewals) {
      return this.getNumberOfQrCodes() >= maxRenewals + 1;
   }

   private boolean isTimeToRenew(TimeProvider time, Duration qrCodeValidity) {
      return this.durationSinceLastRefresh(time)
         .map(durationSinceLastRefresh -> durationSinceLastRefresh.compareTo(qrCodeValidity.dividedBy(2L)) > 0)
         .orElse(true);
   }

   public void refreshQrCode(TimeProvider time, Airlock2FASessionId sessionId, Airlock2FAQrCodeImage qrCode) {
      this.previousSessionId = this.currentSessionId;
      this.currentSessionId = Optional.of(sessionId);
      this.currentQrCode = Optional.of(qrCode);
      this.currentQrCodeFetchedAt = Optional.of(time.getCurrentInstant());
      this.numberOfQrCodes++;
   }

   public Airlock2FAAuthLogData getLogData() {
      return Airlock2FAAuthLogData.airlock2FAAuthLogData().withSessionIdIfPresent(this.currentSessionId);
   }

   public void clearPreviousSession() {
      this.previousSessionId = Optional.empty();
   }

   private Optional<Duration> durationSinceLastRefresh(TimeProvider time) {
      return this.currentQrCodeFetchedAt.map(instant -> Duration.between(instant, time.getCurrentInstant()));
   }

   @Override
   public MapifiedValues mapify() {
      return MapifiedValuesBuilder.mapifiedValues()
         .putIfPresent("currentSessionId", this.currentSessionId.map(Airlock2FASessionId::getSessionId))
         .putIfPresent("previousSessionId", this.previousSessionId.map(Airlock2FASessionId::getSessionId))
         .putIfPresent("qrCode", this.currentQrCode.map(x -> x.toDataUri().toString()))
         .put("numQrCode", this.numberOfQrCodes)
         .putIfPresent("currentQrCodeFetchedAt", this.currentQrCodeFetchedAt.map(MapificationUtils::mapifyInstant))
         .putIfPresent("pollingCache", this.pollingCache.mapify())
         .build();
   }

   public static Airlock2FAUsernamelessQrCodeAuthenticationStepSession demapify(MapifiedValues values) {
      return new Airlock2FAUsernamelessQrCodeAuthenticationStepSession(
         PollingResultCache.demapifyPollingCache(values.getOptionalValues("pollingCache")),
         values.<String>getOptional("currentSessionId").map(Airlock2FASessionId::airlock2FASessionId),
         values.<String>getOptional("previousSessionId").map(Airlock2FASessionId::airlock2FASessionId),
         values.<String>getOptional("qrCode").map(Airlock2FAQrCodeImage::ofDataUri),
         values.<Integer>get("numQrCode"),
         values.demapifyOptional("currentQrCodeFetchedAt", MapificationUtils::demapifyInstant)
      );
   }

   @SuppressFBWarnings(justification = "generated code")
   public Airlock2FAUsernamelessQrCodeAuthenticationStepSession(
      PollingResultCache pollingCache,
      Optional<Airlock2FASessionId> currentSessionId,
      Optional<Airlock2FASessionId> previousSessionId,
      Optional<Airlock2FAQrCodeImage> currentQrCode,
      int numberOfQrCodes,
      Optional<Instant> currentQrCodeFetchedAt
   ) {
      this.pollingCache = pollingCache;
      this.currentSessionId = currentSessionId;
      this.previousSessionId = previousSessionId;
      this.currentQrCode = currentQrCode;
      this.numberOfQrCodes = numberOfQrCodes;
      this.currentQrCodeFetchedAt = currentQrCodeFetchedAt;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FASessionId> getCurrentSessionId() {
      return this.currentSessionId;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FASessionId> getPreviousSessionId() {
      return this.previousSessionId;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FAQrCodeImage> getCurrentQrCode() {
      return this.currentQrCode;
   }

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

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

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

         if (this.numberOfQrCodes != other.numberOfQrCodes) {
            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$currentSessionId = this.currentSessionId;
            Object other$currentSessionId = other.currentSessionId;
            if (this$currentSessionId == null ? other$currentSessionId == null : this$currentSessionId.equals(other$currentSessionId)) {
               Object this$previousSessionId = this.previousSessionId;
               Object other$previousSessionId = other.previousSessionId;
               if (this$previousSessionId == null ? other$previousSessionId == null : this$previousSessionId.equals(other$previousSessionId)) {
                  Object this$currentQrCode = this.currentQrCode;
                  Object other$currentQrCode = other.currentQrCode;
                  if (this$currentQrCode == null ? other$currentQrCode == null : this$currentQrCode.equals(other$currentQrCode)) {
                     Object this$currentQrCodeFetchedAt = this.currentQrCodeFetchedAt;
                     Object other$currentQrCodeFetchedAt = other.currentQrCodeFetchedAt;
                     return this$currentQrCodeFetchedAt == null
                        ? other$currentQrCodeFetchedAt == null
                        : this$currentQrCodeFetchedAt.equals(other$currentQrCodeFetchedAt);
                  } else {
                     return false;
                  }
               } else {
                  return false;
               }
            } else {
               return false;
            }
         } else {
            return false;
         }
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   protected boolean canEqual(Object other) {
      return other instanceof Airlock2FAUsernamelessQrCodeAuthenticationStepSession;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public int hashCode() {
      int PRIME = 59;
      int result = 1;
      result = result * 59 + this.numberOfQrCodes;
      Object $pollingCache = this.pollingCache;
      result = result * 59 + ($pollingCache == null ? 43 : $pollingCache.hashCode());
      Object $currentSessionId = this.currentSessionId;
      result = result * 59 + ($currentSessionId == null ? 43 : $currentSessionId.hashCode());
      Object $previousSessionId = this.previousSessionId;
      result = result * 59 + ($previousSessionId == null ? 43 : $previousSessionId.hashCode());
      Object $currentQrCode = this.currentQrCode;
      result = result * 59 + ($currentQrCode == null ? 43 : $currentQrCode.hashCode());
      Object $currentQrCodeFetchedAt = this.currentQrCodeFetchedAt;
      return result * 59 + ($currentQrCodeFetchedAt == null ? 43 : $currentQrCodeFetchedAt.hashCode());
   }
}
