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

import com.airlock.iam.security.certificate.CertificateStatusChecker;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import org.apache.commons.lang3.time.FastDateFormat;

public final class CertificateValidityCheckerImpl implements CertificateValidityChecker {
   static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss z", Locale.ENGLISH);
   private final Provider<List<CertificateStatusChecker>> certificateStatusCheckers;
   private final Provider<ClientCertificateAuthenticationSettings> settings;

   @Inject
   public CertificateValidityCheckerImpl(
      Provider<List<CertificateStatusChecker>> certificateStatusCheckers, Provider<ClientCertificateAuthenticationSettings> settings
   ) {
      this.certificateStatusCheckers = certificateStatusCheckers;
      this.settings = settings;
   }

   @Override
   public Optional<String> getViolation(X509Certificate certificate) {
      return this.getValidityOrStatusFailure(certificate);
   }

   private Optional<String> getValidityOrStatusFailure(X509Certificate certificate) {
      return ((ClientCertificateAuthenticationSettings)this.settings.get()).checkValidityPeriod()
         ? getValidityFailure(certificate).or(() -> this.getStatusCheckerFailure(certificate))
         : this.getStatusCheckerFailure(certificate);
   }

   private static Optional<String> getValidityFailure(X509Certificate certificate) {
      try {
         certificate.checkValidity();
      } catch (CertificateExpiredException e) {
         return Optional.of("The certificate has expired on %s".formatted(DATE_FORMAT.format(certificate.getNotAfter())));
      } catch (CertificateNotYetValidException e) {
         return Optional.of("The certificate is not valid before %s".formatted(DATE_FORMAT.format(certificate.getNotBefore())));
      }

      return Optional.empty();
   }

   private Optional<String> getStatusCheckerFailure(X509Certificate certificate) {
      return ((List)this.certificateStatusCheckers.get())
         .stream()
         .map(checker -> isRevokedOrHasFailure(certificate, checker))
         .filter(Optional::isPresent)
         .map(Optional::get)
         .findFirst();
   }

   private static Optional<String> isRevokedOrHasFailure(X509Certificate certificate, CertificateStatusChecker checker) {
      try {
         if (checker.isRevoked(certificate)) {
            return Optional.of("The certificate has been revoked.");
         }
      } catch (Exception e) {
         return Optional.of("Error checking certificate status: " + e.getMessage());
      }

      return Optional.empty();
   }
}
