package com.airlock.iam.techclientreg.domain.model.step;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.model.request.ClientTlsCertificate;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.session.FlowAttributeRepository;
import com.airlock.iam.flow.api.domain.model.step.StepError;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.techclientreg.api.domain.model.request.CertificateRequestCredential;
import com.airlock.iam.techclientreg.api.domain.model.request.RequestCredential;
import com.airlock.iam.techclientreg.application.configuration.step.CertificateCredentialExtractionStepConfig;
import com.airlock.iam.techclientreg.domain.model.flow.RequestCredentialFlowAttribute;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Optional;

@LogicForConfig(CertificateCredentialExtractionStepConfig.class)
public class CertificateCredentialExtractionStep implements CredentialExtractionStep {
   private static final ELogger LOG = ELoggerFactory.getLogger(CertificateCredentialExtractionStep.class);
   private final Provider<FlowAttributeRepository> flowAttributes;
   private final Provider<CertificateCredentialExtractionStepConfig> config;
   private final Provider<Optional<ClientTlsCertificate>> mTlsClientCert;

   @Inject
   public CertificateCredentialExtractionStep(
      Provider<FlowAttributeRepository> flowAttributes,
      Provider<CertificateCredentialExtractionStepConfig> config,
      Provider<Optional<ClientTlsCertificate>> mTlsClientCert
   ) {
      this.flowAttributes = flowAttributes;
      this.config = config;
      this.mTlsClientCert = mTlsClientCert;
   }

   @Override
   public StepResult initialize() {
      Optional<ClientTlsCertificate> clientTlsCertificate = (Optional<ClientTlsCertificate>)this.mTlsClientCert.get();
      if (clientTlsCertificate.isEmpty()) {
         LOG.info("No client mTLS certificate found in HTTP request");
         return this.createFailureStepResult();
      } else {
         ClientTlsCertificate certificate = clientTlsCertificate.get();
         Optional<String> certificateFailure = certificate.getFailure();
         if (certificateFailure.isPresent()) {
            LOG.info("Invalid client mTLS certificate found in HTTP request: " + certificateFailure.get());
            return this.createFailureStepResult();
         } else {
            X509Certificate x509Certificate = certificate.getX509Certificate().orElseThrow();
            Principal subjectDn = x509Certificate.getSubjectDN();
            Principal issuerDn = x509Certificate.getIssuerDN();
            LOG.info(
               String.format(
                  "Successfully extracted certificate from request: Subject=%s, Issuer=%s", LogUtil.logSafeQuote(subjectDn), LogUtil.logSafeQuote(issuerDn)
               )
            );
            this.addToFlowAttributes(new CertificateRequestCredential(x509Certificate));
            return this.stepResultFactory().success().build();
         }
      }
   }

   private StepResult createFailureStepResult() {
      return ((CertificateCredentialExtractionStepConfig)this.config.get()).isCertificateRequired()
         ? this.stepResultFactory().fail(CertificateCredentialExtractionStep.CertificateExtractionStepError.CLIENT_CERTIFICATE_MISSING).build()
         : this.stepResultFactory().success().build();
   }

   private void addToFlowAttributes(RequestCredential credential) {
      ((FlowAttributeRepository)this.flowAttributes.get()).putOrMergeAttribute(new RequestCredentialFlowAttribute(credential));
   }

   @Override
   public String getDescription() {
      return "Certificate Credential Extraction Step";
   }

   enum CertificateExtractionStepError implements StepError {
      CLIENT_CERTIFICATE_MISSING;
   }
}
