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

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.certificate.Certificate;
import com.airlock.iam.common.api.domain.model.certificate.OrganizationIdentifier;
import com.airlock.iam.common.domain.model.certificate.CertificateImpl;
import com.airlock.iam.core.misc.authen.credential.CertificateCredential;
import com.airlock.iam.core.misc.authen.credential.Credential;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import java.security.cert.X509Certificate;
import java.util.Optional;
import java.util.function.Consumer;
import lombok.NonNull;
import org.apache.commons.lang3.ClassUtils;

public final class CertificateSubjectOrganizationIdentifierEqualityCredentialVerifier implements CredentialVerifier {
   private static final ELogger LOG = ELoggerFactory.getLogger(CertificateSubjectOrganizationIdentifierEqualityCredentialVerifier.class);
   private final Certificate signingCertificate;
   private final Consumer<String> debugLog;

   public CertificateSubjectOrganizationIdentifierEqualityCredentialVerifier(Certificate signingCertificate) {
      this(signingCertificate, LOG::debug);
   }

   @VisibleForTesting
   CertificateSubjectOrganizationIdentifierEqualityCredentialVerifier(@NonNull Certificate signingCertificate, @NonNull Consumer<String> debugLog) {
      if (signingCertificate == null) {
         throw new NullPointerException("signingCertificate is marked non-null but is null");
      }

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

      this.signingCertificate = signingCertificate;
      this.debugLog = debugLog;
   }

   @Override
   public boolean verify(Credential credential) {
      if (credential instanceof CertificateCredential) {
         X509Certificate credentialCertificate = ((CertificateCredential)credential).getCertificate();
         return this.organizationIdentifiersEqual(new CertificateImpl(credentialCertificate));
      } else {
         this.debugLog("Could not verify credential because it was not a certificate credential. It was " + ClassUtils.getName(credential, "null"));
         return false;
      }
   }

   private boolean organizationIdentifiersEqual(Certificate credentialCertificate) {
      Optional<OrganizationIdentifier> signingOrganizationIdentifier = this.signingCertificate.getSubjectOrganizationIdentifier();
      if (signingOrganizationIdentifier.isEmpty()) {
         this.debugLog("The signing certificate does not contain a single organization identifier.");
         return false;
      } else {
         Optional<OrganizationIdentifier> credentialOrganizationIdentifier = credentialCertificate.getSubjectOrganizationIdentifier();
         if (credentialOrganizationIdentifier.isEmpty()) {
            this.debugLog("The credential certificate does not contain a single organization identifier.");
            return false;
         } else {
            return this.identifiersEqual(signingOrganizationIdentifier.get(), credentialOrganizationIdentifier.get());
         }
      }
   }

   private boolean identifiersEqual(OrganizationIdentifier signingOrgId, OrganizationIdentifier credentialOrgId) {
      if (!signingOrgId.equals(credentialOrgId)) {
         this.debugLog(
            "The signing certificate's organization identifier "
               + LogUtil.logSafeQuote(signingOrgId.getValue())
               + " and the credential certificate's organization identifier "
               + LogUtil.logSafeQuote(credentialOrgId.getValue())
               + " differ."
         );
         return false;
      } else {
         return true;
      }
   }

   private void debugLog(String message) {
      this.debugLog.accept(message);
   }
}
