package com.airlock.iam.factor.domain.service.airlock2fa;

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.user.UserId;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthDevice;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthLogData;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactor;
import com.airlock.iam.factor.domain.model.airlock2fa.AuthenticationStatus;
import com.airlock.iam.factor.domain.model.airlock2fa.UserStatus;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.log.api.signing.SigningLogger;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;

public class Airlock2FALogHelper {
   private static final ELogger LOG = ELoggerFactory.getLogger(Airlock2FALogHelper.class);
   private final Provider<PrincipalInfo> user;
   private final Provider<SigningLogger> signingLogger;
   private final Provider<HelpdeskLogger> helpdeskLogger;

   @Inject
   public Airlock2FALogHelper(Provider<PrincipalInfo> user, Provider<SigningLogger> signingLogger, Provider<HelpdeskLogger> helpdeskLogger) {
      this.user = user;
      this.signingLogger = signingLogger;
      this.helpdeskLogger = helpdeskLogger;
   }

   public void logPreAuthenticationAnswerDetails(Airlock2FAPreAuthenticationService.PreAuthenticationAnswer answer) {
      String message = formatPreAuthenticationAnswerLogMessage(this.userId(), answer);
      ((SigningLogger)this.signingLogger.get()).log(message);
      LOG.info(message);
   }

   public void logAuthenticationAnswerDetails(Airlock2FAAuthenticationService.AuthenticationStatusAnswer answer, Airlock2FAAuthLogData logData) {
      String message = formatAuthenticationStatusAnswerLogMessage(
         this.optionalUserId(),
         answer,
         logData.withUserPresenceVerificationIfPresent(answer.getUserPresenceVerification())
            .withDeviceIdIfPresent(answer.getDeviceId())
            .withAccountIdIfPresent(answer.getAccountId())
      );
      if (answer.getResult() == Airlock2FAAuthenticationService.AuthenticationStatusResult.WAITING) {
         LOG.debug(message);
      } else {
         this.logForUnidentifiedUser(message);
      }
   }

   public void logSuccessfulBypass() {
      UserId userId = this.userId();
      ((SigningLogger)this.signingLogger.get()).log(String.format("Airlock 2FA factor bypass successful for user %s", LogUtil.quote(userId)));
      ((HelpdeskLogger)this.helpdeskLogger.get()).log(userId, "Airlock 2FA factor bypass successful");
   }

   public void logSuccess(Airlock2FAFactor factor, Airlock2FAAuthDevice authDevice, String actionDescription, Airlock2FAAuthLogData logData) {
      UserId userId = this.userId();
      ((SigningLogger)this.signingLogger.get())
         .log(
            String.format(
               "Airlock 2FA %s (%s) successful for user %s using %s device %s",
               actionDescription,
               factor.getDisplayName(),
               LogUtil.quote(userId),
               authDevice.getDeviceType().name(),
               logData
            )
         );
      ((HelpdeskLogger)this.helpdeskLogger.get())
         .log(
            userId,
            String.format(
               "Airlock 2FA %s (%s) successful using %s device %s", actionDescription, factor.getDisplayName(), authDevice.getDeviceType().name(), logData
            )
         );
   }

   public void logFailed(Optional<Airlock2FAFactor> factor, Airlock2FAAuthLogData logData) {
      UserId userId = this.userId();
      String factorName = factor.map(Airlock2FAFactor::getDisplayName).orElse("-");
      ((SigningLogger)this.signingLogger.get())
         .log(String.format("Airlock 2FA authentication (%s) failed for user %s %s", factorName, LogUtil.quote(userId), logData));
      ((HelpdeskLogger)this.helpdeskLogger.get()).log(userId, "Airlock 2FA authentication (" + factorName + ") failed " + logData);
   }

   public void logFailedDueToBypassMode(String actionDescription) {
      UserId userId = this.userId();
      ((SigningLogger)this.signingLogger.get())
         .log(String.format("Airlock 2FA %s failed for user %s due to bypassed Airlock 2FA account", actionDescription, LogUtil.quote(userId)));
      ((HelpdeskLogger)this.helpdeskLogger.get()).log(userId, String.format("Airlock 2FA %s failed due to bypassed Airlock 2FA account", actionDescription));
   }

   public void logFailedDueToCooldownPeriod(String actionDescription, Airlock2FADeviceId deviceId) {
      UserId userId = this.userId();
      ((SigningLogger)this.signingLogger.get())
         .log(
            String.format(
               "Airlock 2FA %s failed for user %s due to using a device (deviceId = \"%s\") during its cooldown period.",
               actionDescription,
               LogUtil.quote(userId),
               deviceId
            )
         );
      ((HelpdeskLogger)this.helpdeskLogger.get())
         .log(userId, String.format("Airlock 2FA %s failed due to using a device (deviceId = \"%s\") during its cooldown period.", actionDescription, deviceId));
   }

   public void logDenyWithDeviceInCooldown(String actionDescription, Airlock2FADeviceId deviceId) {
      UserId userId = this.userId();
      ((SigningLogger)this.signingLogger.get())
         .log(
            String.format(
               "Airlock 2FA %s failed for user %s because it was denied on a device (deviceId = \"%s\") during its cooldown period.",
               actionDescription,
               LogUtil.quote(userId),
               deviceId
            )
         );
      ((HelpdeskLogger)this.helpdeskLogger.get())
         .log(
            userId,
            String.format(
               "Airlock 2FA %s failed because it was denied on a device (deviceId = \"%s\") during its cooldown period.", actionDescription, deviceId
            )
         );
   }

   public void log(String message) {
      UserId userId = this.userId();
      ((SigningLogger)this.signingLogger.get()).log(String.format("%s for user %s", message, LogUtil.quote(userId)));
      ((HelpdeskLogger)this.helpdeskLogger.get()).log(userId, message);
   }

   public void log(String message, Airlock2FAAuthLogData logData) {
      this.optionalUserId().ifPresentOrElse(userId -> this.logForUser(message, logData, userId), () -> this.logForUnidentifiedUser(message + " " + logData));
   }

   private void logForUser(String message, Airlock2FAAuthLogData logData, UserId userId) {
      ((SigningLogger)this.signingLogger.get()).log(String.format("%s for user %s %s", message, LogUtil.quote(userId), logData));
      ((HelpdeskLogger)this.helpdeskLogger.get()).log(userId, message + " " + logData);
   }

   private void logForUnidentifiedUser(String message) {
      ((SigningLogger)this.signingLogger.get()).log(message);
      LOG.info(message);
   }

   public void logAbortingSession(Airlock2FAAuthLogData logData) {
      this.log("Aborting Futurae session", logData);
   }

   public static String formatAuthenticationStatusAnswerLogMessage(
      Optional<UserId> userId, Airlock2FAAuthenticationService.AuthenticationStatusAnswer answer, Airlock2FAAuthLogData logData
   ) {
      String user = userId.<String>map(id -> "user: " + LogUtil.quote(id.getValue())).orElse("unidentified user");
      return "Futurae authentication for "
         + user
         + " with "
         + concatResultWithOptionalStatusString(answer.getResult().getValue(), answer.getStatus().map(AuthenticationStatus::getValue))
         + " "
         + logData;
   }

   public static String formatPreAuthenticationAnswerLogMessage(UserId userId, Airlock2FAPreAuthenticationService.PreAuthenticationAnswer answer) {
      return "Futurae pre-authentication for user: "
         + LogUtil.quote(userId.getValue())
         + " with "
         + concatResultWithOptionalStatusString(answer.getResult().getValue(), answer.getUserStatus().map(UserStatus::getValue));
   }

   private static String concatResultWithOptionalStatusString(String result, Optional<String> status) {
      return "result: " + LogUtil.quote(result) + status.<String>map(s -> " and status: " + LogUtil.quote(s)).orElse("");
   }

   private UserId userId() {
      return this.optionalUserId().orElseThrow(() -> new FailureException("User must be known"));
   }

   private Optional<UserId> optionalUserId() {
      return ((PrincipalInfo)this.user.get()).getKnownIdentity().map(UserIdentity::id);
   }

   public void logSessionStarted(Airlock2FAFactor factor, Airlock2FAAuthLogData logData) {
      this.log(String.format("Futurae session started for %s", factor.getDisplayName() + " factor"), logData);
   }
}
