package com.airlock.iam.common.domain.model.certificate;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
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.authentication.AuthenticationResult;
import com.airlock.iam.common.api.domain.model.credential.RequestAuthentication;
import com.airlock.iam.common.api.domain.model.request.Request;
import com.airlock.iam.common.api.domain.model.user.ProvidedUsername;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.api.domain.model.user.transformation.UsernameTransformation;
import com.airlock.iam.common.application.configuration.certificate.ClientCertificateRequestAuthenticationConfig;
import com.airlock.iam.common.domain.model.authentication.AuthenteeImpl;
import com.airlock.iam.common.domain.model.authentication.AuthenticationResultImpl;
import com.airlock.iam.common.domain.model.contextdata.ContextDataImpl;
import com.airlock.iam.common.domain.model.transform.TransformWithLogContextUpdate;
import com.airlock.iam.core.domain.model.authentication.factor.AuthenticationFactorUsageImpl;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactor;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.log.api.signing.SigningLogger;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Optional;

@LogicForConfig(ClientCertificateRequestAuthenticationConfig.class)
public final class ClientCertificateRequestAuthentication implements RequestAuthentication {
   private static final ELogger LOG = ELoggerFactory.getLogger(ClientCertificateRequestAuthentication.class);
   private final Provider<UsernameTransformation> usernameTransformation;
   private final Provider<CertificateValidityChecker> certificateValidityChecker;
   private final Provider<UserAttributeExtractor> providedUsernameExtractor;
   private final Provider<ClientCertificateAuthenticationSettings> settings;
   private final SigningLogger signingLogger;

   @Inject
   public ClientCertificateRequestAuthentication(
      Provider<UsernameTransformation> usernameTransformation,
      Provider<CertificateValidityChecker> certificateValidityChecker,
      Provider<UserAttributeExtractor> providedUsernameExtractor,
      Provider<ClientCertificateAuthenticationSettings> settings,
      SigningLogger signingLogger
   ) {
      this.usernameTransformation = usernameTransformation;
      this.certificateValidityChecker = certificateValidityChecker;
      this.providedUsernameExtractor = providedUsernameExtractor;
      this.settings = settings;
      this.signingLogger = signingLogger;
   }

   @Override
   public Optional<AuthenticationResult> extractCredentialAndAuthenticate(Request request) {
      Optional<X509Certificate> clientCertificate = request.extractCertificate();
      if (clientCertificate.isEmpty()) {
         return Optional.empty();
      } else {
         Optional<ProvidedUsername> providedUsername = ((UserAttributeExtractor)this.providedUsernameExtractor.get())
            .extractUserAttribute(clientCertificate.get());
         if (providedUsername.isEmpty()) {
            return Optional.empty();
         } else {
            UserId transformedUserId = TransformWithLogContextUpdate.transformAndUpdateLogContext(
               (UsernameTransformation)this.usernameTransformation.get(), providedUsername.get()
            );
            UserIdentity userIdentity = new UserIdentity(transformedUserId, providedUsername.get());
            Optional<String> failure = ((CertificateValidityChecker)this.certificateValidityChecker.get()).getViolation(clientCertificate.get());
            if (failure.isPresent()) {
               this.signingLogger
                  .log("Invalid certificate for extracted username %s: %s", new Object[]{LogUtil.logSafeQuote(transformedUserId), failure.get()});
               return Optional.of(
                  AuthenticationResultImpl.failedAuthenticationWithFactorCheck(
                     userIdentity, true, AuthenticationFactorUsageImpl.failedFactorUsage(KnownAuthenticationFactor.CERTIFICATE)
                  )
               );
            } else {
               LOG.debug(
                  String.format(
                     "Successfully extracted username %s from certificate attribute %s.",
                     LogUtil.logSafeQuote(userIdentity.id()),
                     LogUtil.quote(((ClientCertificateAuthenticationSettings)this.settings.get()).providedUsernameAttribute())
                  )
               );
               return Optional.of(
                  AuthenticationResultImpl.successfulAuthenticationWithFactorCheck(
                     new AuthenteeImpl(userIdentity, Collections.emptySet(), ContextDataImpl.emptyContextData()),
                     AuthenticationFactorUsageImpl.succeededFactorUsage(KnownAuthenticationFactor.CERTIFICATE)
                  )
               );
            }
         }
      }
   }
}
