/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.cr.ui.ws.rs.operation.certificate;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Properties;

import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;
import com.scytl.cryptolib.certificates.service.CertificatesService;
import com.scytl.products.ov.commons.crypto.configuration.CertificateDataBuilder;
import com.scytl.products.ov.commons.crypto.configuration.CertificateParameters;
import com.scytl.products.ov.commons.crypto.configuration.CredentialProperties;
import com.scytl.products.ov.commons.crypto.configuration.X509CertificateGenerator;

public class CertificateRequestSigner {

    public CSRSigningInputProperties getCsrSingingInputProperties(final CredentialProperties credentialProperties) throws IOException {

        final Properties props = new Properties();
        try (InputStream input =
            Thread.currentThread().getContextClassLoader()
                .getResourceAsStream(credentialProperties.getPropertiesFile())) {
            props.load(input);
        }
        String start = props.getProperty(CertificatePropertiesConstants.START_DATE);
        String end = props.getProperty(CertificatePropertiesConstants.END_DATE);
        ZonedDateTime notBefore = ZonedDateTime.ofInstant(Instant.parse(start), ZoneOffset.UTC);
        ZonedDateTime notAfter = ZonedDateTime.ofInstant(Instant.parse(end), ZoneOffset.UTC);

        return new CSRSigningInputProperties(notBefore, notAfter, credentialProperties.getCredentialType());
    }

    public CryptoAPIX509Certificate signCSR(final X509Certificate issuerCA, final PrivateKey issuerPrivateKey,
            final JcaPKCS10CertificationRequest csr, final CSRSigningInputProperties csrSigningInputProperties) throws IOException
            {

        CryptoAPIX509Certificate issuerCert = getCryptoAPIX509CertificateFromX509Certificate(issuerCA);

        final CertificateParameters certificateParameters = new CertificateParameters();

        certificateParameters.setType(csrSigningInputProperties.getType());

        X500Name subject = csr.getSubject();

        RDN cn = subject.getRDNs(BCStyle.CN)[0];
        String subjectCN = IETFUtils.valueToString(cn.getFirst().getValue());

        RDN ou = subject.getRDNs(BCStyle.OU)[0];
        String subjectOU = IETFUtils.valueToString(ou.getFirst().getValue());

        RDN o = subject.getRDNs(BCStyle.O)[0];
        String subjectO = IETFUtils.valueToString(o.getFirst().getValue());

        RDN c = subject.getRDNs(BCStyle.C)[0];
        String subjectC = IETFUtils.valueToString(c.getFirst().getValue());

        certificateParameters.setUserSubjectCn(subjectCN);
        certificateParameters.setUserSubjectOrgUnit(subjectOU);
        certificateParameters.setUserSubjectOrg(subjectO);
        certificateParameters.setUserSubjectCountry(subjectC);

        X509DistinguishedName issuerDN = issuerCert.getSubjectDn();
        certificateParameters.setUserIssuerCn(issuerDN.getCommonName());
        certificateParameters.setUserIssuerOrgUnit(issuerDN.getOrganizationalUnit());
        certificateParameters.setUserIssuerOrg(issuerDN.getOrganization());
        certificateParameters.setUserIssuerCountry(issuerDN.getCountry());

        if (Date.from(csrSigningInputProperties.getNotBefore().toInstant()).before(issuerCert.getNotBefore())) {
            throw new IOException(
                    "The tenant \"start\" time should be strictly after the root \"start\" time");
        }
        if (Date.from(csrSigningInputProperties.getNotAfter().toInstant()).after(issuerCert.getNotAfter())) {
            throw new IOException(
                "The tenant \"end\" time should be strictly before the root \"end\" time");
        }

        certificateParameters.setUserNotBefore(csrSigningInputProperties.getNotBefore());
        certificateParameters.setUserNotAfter(csrSigningInputProperties.getNotAfter());

        PublicKey subjectPublicKey = getPublickeyFromCsr(csr);
        CryptoAPIX509Certificate tenantCACert;
        try {
            X509CertificateGenerator certificateGenerator = createCertificateGenerator();
            tenantCACert = certificateGenerator.generate(certificateParameters, subjectPublicKey, issuerPrivateKey);
        } catch (GeneralCryptoLibException e) {
            throw new IOException("An error occurred while creating the tenant certificate");
        }

        return tenantCACert;
    }

    private PublicKey getPublickeyFromCsr(final JcaPKCS10CertificationRequest csr) throws IOException {
        PublicKey subjectPublicKey;
        try {
            subjectPublicKey = csr.getPublicKey();
        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
            throw new IOException("Could not retrieve the public key from the CSR", e);
        }
        return subjectPublicKey;
    }

    private CryptoAPIX509Certificate getCryptoAPIX509CertificateFromX509Certificate(final X509Certificate issuerCA) throws IOException {
        CryptoAPIX509Certificate issuerCert = null;
        try {
            issuerCert = new CryptoX509Certificate(issuerCA);
        } catch (GeneralCryptoLibException e) {
            throw new IOException(
                "An error occurred while reading the platform root certificate data", e);
        }
        return issuerCert;
    }

    private static X509CertificateGenerator createCertificateGenerator() throws GeneralCryptoLibException {
        CertificatesService certificatesService = new CertificatesService();
        CertificateDataBuilder certificateDataBuilder = new CertificateDataBuilder();

        return new X509CertificateGenerator(certificatesService, certificateDataBuilder);
    }
}
