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

import com.airlock.iam.authentication.api.domain.service.username.IdentificationResult;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.factor.domain.model.fido.FidoAuthenticatorUnverifiedAssertionResponse;
import com.airlock.iam.factor.domain.model.fido.FidoAuthenticatorUnverifiedAttestationResponse;
import com.airlock.iam.factor.domain.model.fido.FidoRegisteredCredential;
import com.airlock.iam.factor.domain.model.fido.FidoRelyingParty;
import com.airlock.iam.factor.domain.model.fido.user.FidoPasswordlessUser;
import com.airlock.iam.factor.domain.model.fido.user.FidoUser;
import com.airlock.iam.factor.domain.model.fido.user.FidoUserPrincipal;
import com.airlock.iam.fido.api.domain.model.FidoRelyingPartyId;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.log.api.signing.SigningLogger;
import jakarta.inject.Inject;
import lombok.NonNull;
import org.apache.commons.lang3.Validate;

public final class FidoLoginLoggerImpl implements FidoLoginLogger {
   private static final String FIDO_AUTHENTICATION = "FIDO authentication";
   private static final String FIDO_PASSWORDLESS_AUTHENTICATION = "FIDO passwordless authentication";
   @NonNull
   private final SigningLogger signingLogger;
   @NonNull
   private final HelpdeskLogger helpdeskLogger;

   @Inject
   public FidoLoginLoggerImpl(@NonNull SigningLogger signingLogger, @NonNull HelpdeskLogger helpdeskLogger) {
      if (signingLogger == null) {
         throw new NullPointerException("signingLogger is marked non-null but is null");
      }

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

      this.signingLogger = signingLogger;
      this.helpdeskLogger = helpdeskLogger;
   }

   @Override
   public void logRegistrationFailure(
      UserId iamUserId,
      FidoRelyingPartyId relyingPartyId,
      FidoAuthenticatorUnverifiedAttestationResponse authenticatorResponse,
      FidoRelyingParty.FidoRegistrationVerificationErrorCode registrationFailure
   ) {
      String message = String.format(
         "Registration of FIDO credential with public-key credential ID '%s' for user '%s' and relying party ID '%s' failed with error code %s.",
         authenticatorResponse.truncatedPublicKeyCredentialId(),
         iamUserId,
         relyingPartyId,
         registrationFailure
      );
      this.signingLogger.log(message);
      this.helpdeskLogger.log(iamUserId, message);
   }

   @Override
   public void logRegistrationSuccess(FidoRegisteredCredential fidoCredential) {
      UserId iamUsername = fidoCredential.getFidoUser().getIamUserId();
      String message = String.format("User '%s' successfully registered a new %s.", iamUsername, fidoCredential);
      this.signingLogger.log(message);
      this.helpdeskLogger.log(iamUsername, message);
   }

   @Override
   public void logPasswordlessMissingUserHandle(FidoAuthenticatorUnverifiedAssertionResponse authenticatorResponse) {
      Validate.isTrue(authenticatorResponse.getUserHandle().isEmpty());
      String message = String.format(
         "FIDO passwordless authentication with provided public key credential ID '%s' failed because user handle not present",
         authenticatorResponse.truncatedPublicKeyCredentialId()
      );
      this.signingLogger.log(message);
   }

   @Override
   public void logPasswordlessUnknownUserHandle(FidoAuthenticatorUnverifiedAssertionResponse authenticatorResponse) {
      Validate.isTrue(authenticatorResponse.getUserHandle().isPresent());
      String message = String.format(
         "FIDO passwordless authentication with provided public key credential ID '%s' failed because user handle '%s' not found",
         authenticatorResponse.truncatedPublicKeyCredentialId(),
         authenticatorResponse.getUserHandle().get()
      );
      this.signingLogger.log(message);
   }

   @Override
   public void logPasswordlessIdentificationFailure(UserId iamUserId, IdentificationResult.IdentificationFailure.IdentificationError error) {
      String logMessage = String.format("Identification of user '%s' failed. ", iamUserId) + String.format("Additional information: %s", error);
      this.signingLogger.log(logMessage);
      this.helpdeskLogger.log(iamUserId, logMessage);
   }

   @Override
   public void logPasswordlessAuthenticationFailure(FidoPasswordlessUser fidoUser, FidoAuthenticatorUnverifiedAssertionResponse authenticatorResponse) {
      String message = messageAuthenticationFailure(fidoUser, authenticatorResponse, "FIDO passwordless authentication");
      this.signingLogger.log(message);
   }

   @Override
   public void logPasswordlessAuthenticationSuccess(FidoRegisteredCredential fidoCredential) {
      this.logAuthenticationSucess(fidoCredential, "FIDO passwordless authentication");
   }

   @Override
   public void logAuthenticationFailure(FidoUserPrincipal fidoUser, FidoAuthenticatorUnverifiedAssertionResponse authenticatorResponse) {
      String message = messageAuthenticationFailure(fidoUser, authenticatorResponse, "FIDO authentication");
      this.signingLogger.log(message);
      this.helpdeskLogger.log(fidoUser.getIamUserId(), message);
   }

   @Override
   public void logAuthenticationSuccess(FidoRegisteredCredential fidoCredential) {
      this.logAuthenticationSucess(fidoCredential, "FIDO authentication");
   }

   @Override
   public void logClientFailure(UserId userId, String message) {
      this.helpdeskLogger.log(userId, "FIDO client reported failure: " + LogUtil.logSafeString(message));
   }

   private void logAuthenticationSucess(FidoRegisteredCredential fidoCredential, String stepDescription) {
      UserId iamUsername = fidoCredential.getFidoUser().getIamUserId();
      String message = String.format("User '%s' successfully authenticated via %s using %s.", iamUsername, stepDescription, fidoCredential);
      this.signingLogger.log(message);
      this.helpdeskLogger.log(iamUsername, message);
   }

   private static String messageAuthenticationFailure(
      FidoUser fidoUser, FidoAuthenticatorUnverifiedAssertionResponse authenticatorResponse, String stepDescription
   ) {
      return String.format(
         "%s for %s using public-key credential ID '%s' for relying party ID '%s' failed",
         stepDescription,
         describeUser(fidoUser),
         authenticatorResponse.truncatedPublicKeyCredentialId(),
         fidoUser.getRelyingPartyId().getId()
      );
   }

   private static String describeUser(FidoUser fidoUser) {
      return fidoUser.getIamUsername()
         .map(userId -> String.format("user '%s'", userId))
         .orElseGet(() -> String.format("user identified by handle '%s'", fidoUser.getUserHandle()));
   }
}
