/*
 * 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.config.it.utils;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.junit.Assert;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.bean.X509CertificateType;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;
import com.scytl.cryptolib.certificates.utils.X509CertificateChainValidator;
import com.scytl.products.ov.datapacks.beans.CertificateParameters;
import com.scytl.products.ov.datapacks.beans.CredentialProperties;
import com.scytl.products.ov.datapacks.helpers.ReplacementsHolder;

/**
 *
 */
public class CertificateValidator {

    private final ReplacementsHolder _replacements;

    public CertificateValidator(final ReplacementsHolder replacements) {
        _replacements = replacements;
    }

    /**
     * @throws FileNotFoundException
     * @throws CertificateException
     * @throws IOException
     * @throws GeneralCryptoLibException
     */
    public CryptoX509Certificate validateCertFromPath(final Path certPath,
            final CredentialProperties credentialProperties)
            throws CertificateException, IOException, GeneralCryptoLibException {

        Assert.assertTrue(Files.exists(certPath));

        CryptoX509Certificate cert = KeyLoader.convertPEMfiletoCryptoX509Certificate(certPath.toString());

        return validateCert(cert, credentialProperties);
    }

    public CryptoX509Certificate validateCert(final CryptoX509Certificate cert,
            final CredentialProperties credentialProperties) throws IOException {

        Properties props = readPropertiesFile(credentialProperties);

        Assert.assertTrue(cert.checkValidity());

        // Check Issuer Attributes if isn't a CA
        if (!credentialProperties.getCredentialType().equals(CertificateParameters.Type.ROOT)) {
            Assert.assertEquals(getProperty(props, "issuer.common.name"), cert.getIssuerDn().getCommonName());
            Assert.assertEquals(getProperty(props, "issuer.country"), cert.getIssuerDn().getCountry());
            Assert.assertEquals(getProperty(props, "issuer.organization"), cert.getIssuerDn().getOrganization());
            Assert.assertEquals(getProperty(props, "issuer.organizational.unit"),
                cert.getIssuerDn().getOrganizationalUnit());
        }

        // Check Subject Attributes
        Assert.assertEquals(getProperty(props, "subject.common.name"), cert.getSubjectDn().getCommonName());
        Assert.assertEquals(getProperty(props, "subject.country"), cert.getSubjectDn().getCountry());
        Assert.assertEquals(getProperty(props, "subject.organization"), cert.getSubjectDn().getOrganization());
        Assert.assertEquals(getProperty(props, "subject.organizational.unit"),
            cert.getSubjectDn().getOrganizationalUnit());

        validateKeyType(credentialProperties.getCredentialType(), cert);

        return cert;
    }

    public List<String> checkChain(CryptoX509Certificate root, CryptoX509Certificate intermediate,
            CryptoX509Certificate leaf) throws GeneralCryptoLibException {

        ArrayList<X509Certificate> arrayListCert = new ArrayList<>();
        ArrayList<X509DistinguishedName> arrayListSubj = new ArrayList<>();
        X509Certificate[] chain = new X509Certificate[1];

        arrayListCert.add(intermediate.getCertificate());
        arrayListSubj.add(intermediate.getSubjectDn());

        X509DistinguishedName[] subjects = new X509DistinguishedName[arrayListSubj.size()];
        subjects = arrayListSubj.toArray(subjects);
        chain = arrayListCert.toArray(chain);

        X509DistinguishedName leafX509DistinguishedName_imported =
            new X509DistinguishedName.Builder(leaf.getSubjectDn().getCommonName(), leaf.getSubjectDn().getCountry())
                .addOrganization(leaf.getSubjectDn().getOrganization())
                .addOrganizationalUnit(leaf.getSubjectDn().getOrganizationalUnit())
                .addLocality(leaf.getSubjectDn().getLocality()).build();

        X509CertificateChainValidator x509CertificateChainValidator =
            new X509CertificateChainValidator(leaf.getCertificate(), X509CertificateType.SIGN,
                leafX509DistinguishedName_imported, null, chain, subjects, root.getCertificate());

        return x509CertificateChainValidator.validate();
    }

    private Properties readPropertiesFile(final CredentialProperties credentialProperties) throws IOException {
        final Properties props = new Properties();

        try (InputStream input = Thread.currentThread().getContextClassLoader()
            .getResourceAsStream(credentialProperties.getPropertiesFile())) {
            props.load(input);
        }

        return props;
    }

    private String getProperty(final Properties properties, final String name) {
        String value = properties.getProperty(name);
        return _replacements.applyReplacements(value);
    }

    private boolean validateKeyType(final CertificateParameters.Type credentialType, final CryptoX509Certificate cert) {

        boolean hasExpectedKeyUsage;
        switch (credentialType) {
        case ROOT:
            hasExpectedKeyUsage = cert.isCertificateType(X509CertificateType.CERTIFICATE_AUTHORITY);
            break;
        case INTERMEDIATE:
            hasExpectedKeyUsage = cert.isCertificateType(X509CertificateType.SIGN);
            break;
        // Default is for sign or encryption purposes
        default:
            hasExpectedKeyUsage = cert.isCertificateType(X509CertificateType.ENCRYPT);
            break;
        }

        return hasExpectedKeyUsage;
    }
}
