package com.airlock.iam.oauth2.infrastructure.service.clientauthentication;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.model.certificate.CertificateLogUtil;
import com.airlock.iam.common.domain.model.request.ClientTlsCertificate;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.OAuth2AuthClientId;
import com.airlock.iam.oauth2.api.domain.model.http.OAuth2HttpRequest;
import com.airlock.iam.oauth2.api.domain.service.clientauthentication.OAuth2ClientAuthenticationParser;
import com.airlock.iam.oauth2.domain.model.OAuth2Param;
import com.airlock.iam.oauth2.domain.model.clientauthentication.OAuth2ClientCertificateAuthenticationPayload;
import java.security.cert.X509Certificate;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;

public final class OAuth2ClientMTLSAuthenticationParser implements OAuth2ClientAuthenticationParser<OAuth2ClientCertificateAuthenticationPayload> {
   private final Optional<ClientTlsCertificate> mTlsClientCert;
   private final ELogger logger;

   public OAuth2ClientMTLSAuthenticationParser(Optional<ClientTlsCertificate> mTlsClientCert, ELogger logger) {
      this.mTlsClientCert = mTlsClientCert;
      this.logger = logger;
   }

   @Override
   public Optional<OAuth2ClientCertificateAuthenticationPayload> parse(OAuth2HttpRequest request) {
      return this.findClientId(request).flatMap(this::withCertificate);
   }

   private Optional<OAuth2AuthClientId> findClientId(OAuth2HttpRequest request) {
      return request.getParameter(OAuth2Param.CLIENT_ID.getParameterName()).filter(StringUtils::isNotBlank).map(c -> {
         this.logger.debug("Found clientId for authentication: " + LogUtil.quote(c));
         return Optional.of(OAuth2AuthClientId.oAuth2AuthClientId(c));
      }).orElseGet(() -> {
         this.logger.info("Could not find client_id parameter!");
         return Optional.empty();
      });
   }

   private Optional<OAuth2ClientCertificateAuthenticationPayload> withCertificate(OAuth2AuthClientId clientId) {
      return this.findValidCertificate()
         .map(cert -> OAuth2ClientCertificateAuthenticationPayload.builder().clientId(clientId).certificate(cert.getX509Certificate().orElseThrow()).build());
   }

   private Optional<ClientTlsCertificate> findValidCertificate() {
      if (this.mTlsClientCert.isEmpty()) {
         this.logger.info("Did not find a certificate in the request");
         return Optional.empty();
      } else {
         ClientTlsCertificate certificate = this.mTlsClientCert.get();
         Optional<String> certificateFailure = certificate.getFailure();
         if (certificateFailure.isPresent()) {
            this.logger.info("Invalid client certificate found in request: " + certificateFailure.get());
            return Optional.empty();
         } else {
            this.logFoundCertificate(certificate.getX509Certificate().orElseThrow());
            return this.mTlsClientCert;
         }
      }
   }

   private void logFoundCertificate(X509Certificate certificate) {
      if (this.logger.isDebugEnabled()) {
         this.logger.debug("Found the certificate in the request:" + CertificateLogUtil.certificateLogString(certificate));
      }
   }
}
