package com.airlock.iam.oauth2.domain.service.token;

import com.airlock.iam.common.api.domain.model.certificate.ClientCertificate;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.oauth2.domain.model.credential.OAuth2TokenBindingClientCertificateCredential;
import com.airlock.iam.oauth2.domain.model.credential.OAuth2TokenBindingCredential;
import com.airlock.iam.oauth2.domain.model.token.OAuth2ClientCertificateBindingConfirmationMethod;
import com.airlock.iam.oauth2.domain.model.token.OAuth2TokenBinding;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Optional;
import java.util.Base64.Encoder;

public final class OAuth2ClientCertificateTokenBindingVerifier implements OAuth2TokenBindingVerifier {
   private static final Encoder ENCODER = Base64.getUrlEncoder();
   private final ELogger logger;

   @Override
   public boolean verify(OAuth2TokenBinding tokenBinding, Optional<OAuth2TokenBindingCredential> credential) {
      Optional<OAuth2ClientCertificateBindingConfirmationMethod> method = OAuth2ClientCertificateBindingConfirmationMethod.method(tokenBinding.getMethod());
      if (method.isEmpty()) {
         this.logger.info("Invalid token client certificate binding method found. Rejecting token.");
         return false;
      } else {
         Optional<X509Certificate> certificate = certificate(credential);
         if (certificate.isEmpty()) {
            this.logger.info("Token was bound to client certificate, but client certificate is missing in the TLS connection. Rejecting token.");
            return false;
         } else if (!this.verifyFingerPrint(tokenBinding, certificate.get(), method.get())) {
            this.logger.info("Token was bound to different mTLS client certificate than used as client authentication. Rejecting token.");
            return false;
         } else {
            this.logger.debug("Successfully verified mTLS client certificate bound to OAuth 2.0 Access Token.");
            return true;
         }
      }
   }

   private boolean verifyFingerPrint(OAuth2TokenBinding tokenBinding, X509Certificate certificate, OAuth2ClientCertificateBindingConfirmationMethod method) {
      ClientCertificate clientCertificate = new ClientCertificate(certificate);
      String fingerprint = clientCertificate.getFingerprint(method.getAlgorithm(), ENCODER);
      return fingerprint.equals(tokenBinding.getProof());
   }

   private static Optional<X509Certificate> certificate(Optional<OAuth2TokenBindingCredential> clientAuthenticationPayload) {
      return clientAuthenticationPayload.filter(OAuth2TokenBindingClientCertificateCredential.class::isInstance)
         .map(OAuth2TokenBindingClientCertificateCredential.class::cast)
         .map(OAuth2TokenBindingClientCertificateCredential::getCertificate);
   }

   @SuppressFBWarnings(justification = "generated code")
   public static OAuth2ClientCertificateTokenBindingVerifier.OAuth2ClientCertificateTokenBindingVerifierBuilder builder() {
      return new OAuth2ClientCertificateTokenBindingVerifier.OAuth2ClientCertificateTokenBindingVerifierBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   private OAuth2ClientCertificateTokenBindingVerifier(ELogger logger) {
      this.logger = logger;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2ClientCertificateTokenBindingVerifierBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ELogger logger;

      @SuppressFBWarnings(justification = "generated code")
      OAuth2ClientCertificateTokenBindingVerifierBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientCertificateTokenBindingVerifier.OAuth2ClientCertificateTokenBindingVerifierBuilder logger(ELogger logger) {
         this.logger = logger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientCertificateTokenBindingVerifier build() {
         return new OAuth2ClientCertificateTokenBindingVerifier(this.logger);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2ClientCertificateTokenBindingVerifier.OAuth2ClientCertificateTokenBindingVerifierBuilder(logger=" + this.logger + ")";
      }
   }
}
