package de.ivu.elect.nl.x509;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.function.Consumer;

import sun.security.provider.X509Factory;
import sun.security.x509.AlgorithmId;
import sun.security.x509.CertificateAlgorithmId;
import sun.security.x509.CertificateSerialNumber;
import sun.security.x509.CertificateValidity;
import sun.security.x509.CertificateVersion;
import sun.security.x509.CertificateX509Key;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;

public class NlX509 {

   public static final String LS = "\n";

   public void exportPublicKeyAsX509(OutputStream outputStream,
                                     PrivateKey privateKey,
                                     PublicKey publicKey,
                                     NlX509Metadata nlX509Metadata) throws Exception {
//      try {



      byte[] encoded = createCertificate(nlX509Metadata, privateKey, publicKey).getEncoded();
      String sb = X509Factory.BEGIN_CERT + LS + new String(Base64.getMimeEncoder(64, LS.getBytes()).encode(encoded), StandardCharsets.UTF_8) + LS + X509Factory.END_CERT;
      outputStream.write(sb.getBytes(StandardCharsets.UTF_8));

//      } catch (CertificateException certEx) {
//         throw new RuntimeException("de.ivu.elect.nl.x509.exception.certificateInstantiate", certEx); // Fehler beim Instanziieren des Zertifikats
//      } catch (IOException ioEx) {
//         throw new Exception("de.ivu.elect.nl.x509.exception.certificateGenerate", ioEx); // Fehler bei der Zertifikatsgenerierung
//      } catch (NoSuchProviderException noProvEx) {
//         throw new Exception("de.ivu.elect.nl.x509.exception.securityProviderNotDefined", noProvEx); // Security Provider nicht definiert
//      } catch (NoSuchAlgorithmException noAlgoEx) {
//         throw new Exception("de.ivu.elect.nl.x509.exception.signAlgorithmUnknown", noAlgoEx); // ungueltiger Signieralgorithmus
//      } catch (InvalidKeyException invKeyEx) {
//         throw new Exception("de.ivu.elect.nl.x509.exception.privateKeyInvalid", invKeyEx); // ungueltiger privater Schluessel
//      } catch (SignatureException sigEx) {
//         throw new Exception("de.ivu.elect.nl.x509.exception.signCertificate", sigEx); // Fehler beim Signieren des Zertfikats
//      }
   }

   public PublicKey importPublicKeyFromX509(InputStream inputStream, Consumer<NlX509Metadata> metadataConsumer) throws CertificateException {
      X509Certificate certificate = convertToX509Certificate(inputStream);
      Principal subject = certificate.getSubjectDN();
      metadataConsumer.accept(NlX509Metadata.fromDistinguihedName(subject.getName(), certificate.getNotBefore(), certificate.getNotAfter(),
          certificate.getSigAlgName()));
      return certificate.getPublicKey();
   }

   private X509Certificate convertToX509Certificate(InputStream is) throws CertificateException {
      return (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(is);
   }

   private X509Certificate createCertificate(NlX509Metadata nlX509Metadata,
                                             PrivateKey privateKey,
                                             PublicKey publicKey)
       throws CertificateException, IOException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
      X509CertInfo info = new X509CertInfo();

      X500Name owner = new X500Name(nlX509Metadata.getDistinguishedName());
      info.set(X509CertInfo.SUBJECT, owner);
      info.set(X509CertInfo.ISSUER, owner);

      CertificateValidity interval = new CertificateValidity(nlX509Metadata.getValidFrom(), nlX509Metadata.getValidTill());
      info.set(X509CertInfo.VALIDITY, interval);

      info.set(X509CertInfo.SERIAL_NUMBER, createSerial(nlX509Metadata));

      info.set(X509CertInfo.KEY, new CertificateX509Key(publicKey));
      info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));

      AlgorithmId algo = new AlgorithmId(AlgorithmId.sha512WithRSAEncryption_oid);
      info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));

      // Sign the cert to identify the algorithm that's used.
      X509CertImpl cert = new X509CertImpl(info);
      cert.sign(privateKey, nlX509Metadata.getSignAlgorithm());

      // Update the algorithm, and resign.
      algo = (AlgorithmId) cert.get(X509CertImpl.SIG_ALG);
      info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
      cert = new X509CertImpl(info);
      cert.sign(privateKey, nlX509Metadata.getSignAlgorithm());

      return cert;
   }

   private CertificateSerialNumber createSerial(NlX509Metadata nlX509Metadata) {
      try {
         MessageDigest digest = MessageDigest.getInstance("SHA-1");
         digest.reset();
         digest.update(nlX509Metadata.toString().getBytes(StandardCharsets.UTF_8));
         String sha1 = String.format("%040x", new BigInteger(1, digest.digest()));
         BigInteger bigInteger = new BigInteger(sha1.getBytes(StandardCharsets.UTF_8));
         return new CertificateSerialNumber(bigInteger);
         // return new CertificateSerialNumber(new ByteArrayInputStream(nlX509Metadata.substring(0, 10).getBytes(StandardCharsets.UTF_8)));
      } catch (Exception e) {
         throw new RuntimeException(e.getMessage(), e);
      }

   }

}
