package com.airlock.iam.flow.shared.domain.model.airlock2fa;

import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultBuilder;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.core.misc.util.qrcode.QrCodeUtilities;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthDevice;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FADevices;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAMultiNumberedChallenge;
import com.airlock.iam.factor.domain.model.airlock2fa.activation.Airlock2FAActivationSession;
import com.airlock.iam.factor.domain.model.airlock2fa.qrcode.Airlock2FAExchangeTokenData;
import com.airlock.iam.factor.domain.model.airlock2fa.qrcode.Airlock2FAQrCodeImage;
import com.google.common.annotations.VisibleForTesting;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.Instant;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public final class StepResultHelper {
   private static final String CHALLENGE_IMAGE_KEY = "challengeImage";
   private static final String APP_APPROVAL_URL_KEY = "appApprovalUrl";
   private static final String APP_ACTIVATION_URL_KEY = "appActivationUrl";
   private static final String MULTI_NUMBERED_CHALLENGE_KEY = "multiNumberedChallenge";
   private static final String CHOICE_ID_KEY = "id";
   private static final String CHOICE_NAME_KEY = "displayName";
   private static final String CHOICE_TYPE_KEY = "deviceType";

   public static boolean hasSessionTimeout(Optional<Instant> startTime, Instant currentTime, Duration timeoutDuration) {
      if (startTime.isEmpty()) {
         throw new FailureException("No session start time found in step session.");
      } else {
         return currentTime.isAfter(startTime.get().plus(timeoutDuration));
      }
   }

   public static List<Map<String, String>> createAdditionalAttribute(Airlock2FADevices devices) {
      return devices.stream().map(StepResultHelper::toDeviceChoiceMap).toList();
   }

   public static AuthenticationStepResultBuilder addOfflineQrCodeChallenge(AuthenticationStepResultBuilder builder, Airlock2FAOfflineQrCodeChallenge challenge) {
      return builder.addAdditionalAttribute("challengeImage", challenge.getChallengeImage().getEncodedImage());
   }

   public static AuthenticationStepResultBuilder addMultiNumberedChallenge(AuthenticationStepResultBuilder builder, Airlock2FAMultiNumberedChallenge challenge) {
      return builder.addAdditionalAttribute("multiNumberedChallenge", challenge.value());
   }

   public static AuthenticationStepResultBuilder addOnlineQrCodeChallenge(AuthenticationStepResultBuilder builder, Airlock2FAOnlineQrCodeChallenge challenge) {
      return builder.addAdditionalAttribute("challengeImage", challenge.getChallengeImage().getEncodedImage())
         .addAdditionalAttribute("appApprovalUrl", challenge.getMobileAuthUri().uri());
   }

   public static AuthenticationStepResultBuilder addAuthenticationShortLivedQrCodeChallenge(
      AuthenticationStepResultBuilder builder, List<Airlock2FAExchangeTokenData> sessionExchangeTokens, Instant currentTime, Duration overlap
   ) {
      return addShortLivedQrCodeChallengeAttributes(builder, sessionExchangeTokens, currentTime, overlap, "appApprovalUrl");
   }

   public static AuthenticationStepResultBuilder addEnrollmentQrCodeChallenge(
      AuthenticationStepResultBuilder builder, Airlock2FAActivationSession activationSession
   ) {
      return builder.addAdditionalAttribute("challengeImage", activationSession.getQrCode().getEncodedImage())
         .addAdditionalAttribute("appActivationUrl", activationSession.getAppDeviceActivationUrl().getUri());
   }

   public static AuthenticationStepResultBuilder addEnrollmentShortLivedQrCodeChallenge(
      AuthenticationStepResultBuilder builder, List<Airlock2FAExchangeTokenData> enrollmentExchangeTokens, Instant currentTime, Duration overlap
   ) {
      return addShortLivedQrCodeChallengeAttributes(builder, enrollmentExchangeTokens, currentTime, overlap, "appActivationUrl");
   }

   private static AuthenticationStepResultBuilder addShortLivedQrCodeChallengeAttributes(
      AuthenticationStepResultBuilder builder, List<Airlock2FAExchangeTokenData> shortLivedQrCodes, Instant currentTime, Duration overlap, String urlKey
   ) {
      Optional<Airlock2FAExchangeTokenData> currentExchangeToken = determineActiveQrCode(shortLivedQrCodes, currentTime, overlap);
      if (currentExchangeToken.isPresent()) {
         BufferedImage generatedImage = QrCodeUtilities.createQrCodeImage(currentExchangeToken.get().getQrCodeContent(), 256, 256);
         Airlock2FAQrCodeImage airlock2FAQrCodeImage = Airlock2FAQrCodeImage.ofDataUri(QrCodeUtilities.createPngDataUriFromImage(generatedImage));
         return builder.addAdditionalAttribute("challengeImage", airlock2FAQrCodeImage.getEncodedImage())
            .addAdditionalAttribute(urlKey, currentExchangeToken.get().getTokenUri());
      } else {
         throw new FailureException("No valid short-lived Online QR Code found for the current time.");
      }
   }

   @VisibleForTesting
   static Optional<Airlock2FAExchangeTokenData> determineActiveQrCode(List<Airlock2FAExchangeTokenData> qrCodes, Instant timeNow, Duration overlap) {
      if (qrCodes.isEmpty()) {
         throw new FailureException("No short-lived Online QR Codes provided.");
      }

      List<Airlock2FAExchangeTokenData> sortedQrCodes = sortQrCodesByExpiryTime(qrCodes);
      return sortedQrCodes.stream()
         .filter(qrCode -> isValidToDisplay(qrCode, timeNow, overlap))
         .findFirst()
         .or(() -> lastCodeIfNotExpired(sortedQrCodes, timeNow));
   }

   private static List<Airlock2FAExchangeTokenData> sortQrCodesByExpiryTime(List<Airlock2FAExchangeTokenData> qrCodes) {
      return qrCodes.stream().sorted(Comparator.comparing(Airlock2FAExchangeTokenData::getExpiryTime)).toList();
   }

   private static boolean isValidToDisplay(Airlock2FAExchangeTokenData qrCode, Instant timeNow, Duration overlap) {
      return timeNow.isBefore(qrCode.getExpiryTime().minus(overlap));
   }

   private static Optional<Airlock2FAExchangeTokenData> lastCodeIfNotExpired(List<Airlock2FAExchangeTokenData> sortedQrCodes, Instant timeNow) {
      return timeNow.isBefore(sortedQrCodes.getLast().getExpiryTime()) ? Optional.of(sortedQrCodes.getLast()) : Optional.empty();
   }

   private static Map<String, String> toDeviceChoiceMap(Airlock2FAAuthDevice device) {
      return Map.of("id", device.getId().id(), "displayName", device.getDisplayName().getValue(), "deviceType", device.getDeviceType().name());
   }

   private StepResultHelper() {
      throw new IllegalStateException();
   }
}
