package com.airlock.iam.factor.domain.model.fido;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.factor.application.configuration.fido.FidoAttestationCertificateTrustVerifierConfig;
import com.airlock.iam.factor.domain.model.fido.registration.FidoAttestationObject;
import jakarta.inject.Inject;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Optional;

@LogicForConfig(FidoAttestationCertificateTrustVerifierConfig.class)
public class FidoAttestationCertificateTrustVerifier implements FidoAttestationVerifier {
   private static final ELogger LOG = ELoggerFactory.getLogger(FidoAttestationCertificateTrustVerifier.class);
   private final CertificateChainVerifier certificateChainVerifier;

   @Inject
   FidoAttestationCertificateTrustVerifier(CertificateChainVerifier certificateChainVerifier) {
      this.certificateChainVerifier = certificateChainVerifier;
   }

   @Override
   public boolean verify(FidoAttestationObject attestationObject) {
      if (!this.hasSupportedAttestationFormat(attestationObject.attestationStatementFormat())) {
         this.logUnexpectedAttestationStatementFormat(attestationObject.attestationStatementFormat());
         return false;
      }

      LOG.debug(
         String.format(
            "Verifying trust in attestation certificate with format '%s' for public key credential ID '%s'",
            attestationObject.attestationStatementFormat().orElse(null),
            attestationObject.publicKeyCredentialId()
         )
      );
      Result<X509Certificate, String> result = this.certificateChainVerifier.findTrustAnchor(attestationObject.attestationCertificateChain());
      if (result.isFailure()) {
         LOG.info("Failed to verify FIDO attestation statement. Cause: " + result.getFailure());
      }

      return result.isPresent();
   }

   private boolean hasSupportedAttestationFormat(Optional<FidoKnownAttestationStatementFormat> attestationStatementFormat) {
      return attestationStatementFormat.stream().anyMatch(this.supportedAttestationFormat()::contains);
   }

   private List<FidoKnownAttestationStatementFormat> supportedAttestationFormat() {
      return List.of(FidoKnownAttestationStatementFormat.PACKED, FidoKnownAttestationStatementFormat.FIDO_U2F);
   }

   private void logUnexpectedAttestationStatementFormat(Optional<FidoKnownAttestationStatementFormat> attestationStatementFormat) {
      LOG.info(
         String.format(
            "Failed to verify FIDO attestation statement. Cause: Expected attestation statement format to be one of '%s', but was '%s'.",
            this.supportedAttestationFormat(),
            LogUtil.logSafeString(attestationStatementFormat.orElse(null))
         )
      );
   }
}
