/*
 * 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.cryptolib.interoperation.js.commands.certificates;

import java.security.KeyPair;
import java.security.cert.Certificate;
import java.util.Date;

import javax.script.ScriptException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.utils.AsymmetricTestDataGenerator;
import com.scytl.cryptolib.certificates.bean.CertificateData;
import com.scytl.cryptolib.certificates.bean.RootCertificateData;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.constants.X509CertificateTestConstants;
import com.scytl.cryptolib.certificates.service.CertificatesService;
import com.scytl.cryptolib.certificates.utils.X509CertificateTestDataGenerator;
import com.scytl.cryptolib.interoperation.js.commands.Command;
import com.scytl.cryptolib.interoperation.js.commands.CommandException;
import com.scytl.cryptolib.interoperation.js.constants.CertificateConstants;
import com.scytl.cryptolib.interoperation.js.constants.CommonConstants;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;

/**
 * Tests the Java-JavaScript interoperation for certificate chain applications.
 */
public class TestCertificateChain extends Command {

    private static final Logger LOGGER =
        LoggerFactory.getLogger(TestCertificateChain.class);

    private static final String LEAF_CERTIFICATE_TYPE = "Sign";

    private final Object _namespace;

    private final CertificatesService _certificatesService;

    private static KeyPair _rootKeyPair;

    private static String _rootPrivateKeyPem;

    private static KeyPair _intermediateKeyPair;

    private static String _intermediatePrivateKeyPem;

    private static KeyPair _leafKeyPair;

    private static String _leafPrivateKeyPem;

    private static RootCertificateData _rootCertificateData;

    private static CertificateData _intermediateCertificateData;

    private static CertificateData _leafCertificateData;

    private static String[] _intermediateSubjectDnFields;

    private static String[] _leafSubjectDnFields;

    private static String _leafTimeReferenceStr;

    /**
     * Default constructor.
     *
     * @throws CommandException
     *             if the script engine cannot evaluate the certificate
     *             interoperation tests implemented in JavaScript.
     */
    public TestCertificateChain() throws CommandException {
        super(CertificateConstants.JS_CERTIFICATE_CHAIN_TEST_PATH);

        _namespace = _engine
            .get(CertificateConstants.JS_CERTIFICATE_CHAIN_TEST_NAMESPACE);

        clearExceptionData();

        try {
            _certificatesService = new CertificatesService();

            generateInputData();
        } catch (GeneralCryptoLibException e) {
            throw new CommandException.Builder().addCause(e).build();
        }
    }

    /**
     * Generates a certificate chain using Java and validates the result using
     * JavaScript. If no exceptions are thrown, the certificate chain can be
     * validated and then the interoperation test for certificate chain
     * applications was successful.
     *
     * @throws CommandException
     *             if a JavaScript method cannot be found, a cryptographic
     *             operation fails, or the certificate chain cannot be
     *             validated.
     */
    @Override
    public void execute() throws CommandException {

        String[] certificatePemChain = generateUsingJava();

        validateUsingJavaScript(certificatePemChain);

        LOGGER.info(CommonConstants.LOGGER_PROMPT
            + "INTEROPERATION TEST FOR CERTIFICATE CHAIN WAS SUCCESSFUL.");
    }

    private void generateInputData() throws GeneralCryptoLibException {

        _rootKeyPair = AsymmetricTestDataGenerator.getKeyPairForSigning();
        _rootPrivateKeyPem =
            PemUtils.privateKeyToPem(_rootKeyPair.getPrivate());
        putExceptionData("Root private key (PEM format) [Java]",
            _rootPrivateKeyPem);

        _intermediateKeyPair =
            AsymmetricTestDataGenerator.getKeyPairForSigning();
        _intermediatePrivateKeyPem =
            PemUtils.privateKeyToPem(_intermediateKeyPair.getPrivate());
        putExceptionData("Intermediate private key (PEM format) [Java]",
            _intermediatePrivateKeyPem);

        _leafKeyPair = AsymmetricTestDataGenerator.getKeyPairForSigning();
        _leafPrivateKeyPem =
            PemUtils.privateKeyToPem(_leafKeyPair.getPrivate());
        putExceptionData("Leaf private key (PEM format) [Java]",
            _leafPrivateKeyPem);

        _rootCertificateData = X509CertificateTestDataGenerator
            .getRootCertificateData(_rootKeyPair);
        _intermediateCertificateData = X509CertificateTestDataGenerator
            .getIntermediateCertificateData(_intermediateKeyPair);
        _leafCertificateData = X509CertificateTestDataGenerator
            .getLeafCertificateData(_leafKeyPair);

        _intermediateSubjectDnFields =
            getDnFields(_leafCertificateData.getIssuerDn());
        putExceptionData(
            "Intermediate certificate subject common name [Java]",
            _intermediateSubjectDnFields[0]);
        putExceptionData("Intermediate certificate subject country [Java]",
            _intermediateSubjectDnFields[1]);
        putExceptionData(
            "Intermediate certificate subject organizational unit [Java]",
            _intermediateSubjectDnFields[2]);
        putExceptionData(
            "Intermediate certificate subject organization [Java]",
            _intermediateSubjectDnFields[3]);

        _leafSubjectDnFields =
            getDnFields(_leafCertificateData.getSubjectDn());
        putExceptionData("Leaf certificate subject common name [Java]",
            _leafSubjectDnFields[0]);
        putExceptionData("Leaf certificate subject country [Java]",
            _leafSubjectDnFields[1]);
        putExceptionData(
            "Leaf certificate subject organizational unit [Java]",
            _leafSubjectDnFields[2]);
        putExceptionData("Leaf certificate subject organization [Java]",
            _leafSubjectDnFields[3]);

        _leafTimeReferenceStr =
            getDateAsEpochTimeString(X509CertificateTestDataGenerator
                .getDateWithinValidityPeriod());
        putExceptionData(
            "Leaf certificate time reference (Epoch time format) [Java]",
            _leafTimeReferenceStr);
    }

    private String[] generateUsingJava() throws CommandException {

        LOGGER.info(CommonConstants.LOGGER_PROMPT
            + "Generating certificate chain, using Java...");

        try {
            Certificate rootCertificate =
                _certificatesService
                    .createRootAuthorityX509Certificate(
                        _rootCertificateData, _rootKeyPair.getPrivate())
                    .getCertificate();
            String rootCertificatePem =
                PemUtils.certificateToPem(rootCertificate);
            putExceptionData("Root certificate (PEM format) [Java]",
                rootCertificatePem);

            Certificate intermediateCertificate = _certificatesService
                .createIntermediateAuthorityX509Certificate(
                    _intermediateCertificateData,
                    _rootKeyPair.getPrivate())
                .getCertificate();
            String intermediateCertificatePem =
                PemUtils.certificateToPem(intermediateCertificate);
            putExceptionData(
                "Intermediate certificate (PEM format) [Java]",
                intermediateCertificatePem);

            Certificate leafCertificate =
                _certificatesService
                    .createSignX509Certificate(_leafCertificateData,
                        _intermediateKeyPair.getPrivate())
                    .getCertificate();
            String leafCertificatePem =
                PemUtils.certificateToPem(leafCertificate);
            putExceptionData("Leaf certificate (PEM format) [Java]",
                leafCertificatePem);

            String[] certificatePemChain = new String[3];
            certificatePemChain[0] = leafCertificatePem;
            certificatePemChain[1] = intermediateCertificatePem;
            certificatePemChain[2] = rootCertificatePem;

            return certificatePemChain;
        } catch (GeneralCryptoLibException e) {
            throw buildExceptionWithData(
                "Could not generate certificate chain, using Java.", e);
        }
    }

    private void validateUsingJavaScript(
            final String[] certificatePemChain) throws CommandException {

        LOGGER.info(CommonConstants.LOGGER_PROMPT
            + "Validating certificate chain, using JavaScript...");

        try {
            getInvocable().invokeMethod(_namespace,
                CertificateConstants.JS_CERTIFICATE_CHAIN_VALIDATION_FUNCTION,
                certificatePemChain[2], certificatePemChain[1],
                certificatePemChain[0], _intermediateSubjectDnFields,
                _leafSubjectDnFields, LEAF_CERTIFICATE_TYPE,
                _leafTimeReferenceStr);
        } catch (NoSuchMethodException e) {
            throw new CommandException.Builder()
                .addMessage(
                    "Attempt by script engine to call non-existent function "
                        + CertificateConstants.JS_CERTIFICATE_CHAIN_TEST_NAMESPACE
                        + "."
                        + CertificateConstants.JS_CERTIFICATE_CHAIN_VALIDATION_FUNCTION
                        + " in resources file "
                        + CertificateConstants.JS_CERTIFICATE_CHAIN_TEST_PATH)
                .addCause(e).build();
        } catch (ScriptException e) {
            throw buildExceptionWithData(
                "Could not validate certificate chain, using JavaScript.",
                e);
        }
    }

    private String[] getDnFields(
            final X509DistinguishedName distinguishedName) {

        String[] dnFields = new String[4];
        dnFields[0] = distinguishedName.getCommonName();
        dnFields[1] = distinguishedName.getCountry();
        dnFields[2] = distinguishedName.getOrganizationalUnit();
        dnFields[3] = distinguishedName.getOrganization();

        return dnFields;
    }

    private String getDateAsEpochTimeString(final Date date) {

        return String.valueOf(date.getTime()).substring(0,
            X509CertificateTestConstants.EPOCH_TIME_LENGTH);
    }
}
