/*
 * 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.crs.services.validators;

import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.Security;
import java.util.Calendar;
import java.util.Date;
import java.util.Properties;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.BeforeClass;
import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.bean.CertificateData;
import com.scytl.cryptolib.certificates.bean.RootCertificateData;
import com.scytl.cryptolib.certificates.bean.ValidityDates;
import com.scytl.cryptolib.certificates.bean.X509CertificateType;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.configuration.X509CertificateValidationData;
import com.scytl.cryptolib.certificates.configuration.X509CertificateValidationResult;
import com.scytl.cryptolib.certificates.configuration.X509CertificateValidationType;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;
import com.scytl.cryptolib.certificates.service.CertificatesService;
import com.scytl.cryptolib.certificates.utils.X509CertificateValidator;
import com.scytl.products.ov.commons.beans.validation.CertificateValidationResult;
import com.scytl.products.ov.commons.beans.validation.CryptographicOperationException;
import com.scytl.products.ov.cr.services.domain.model.platform.CrCertificateValidationServiceImpl;

public class CertificateValidationServiceTest {

    private static CrCertificateValidationServiceImpl _target;

    private static KeyPair _rootKeyPair;

    private static KeyPair _userKeyPair;

    private static String _rootSubjectCn;

    private static String _rootSubjectOrgUnit;

    private static String _rootSubjectOrg;

    private static String _rootSubjectLocality;

    private static String _rootSubjectCountry;

    private static X509DistinguishedName _rootSubjectDn;

    private static int _rootNumYearsValidity;

    private static Date _rootNotBefore;

    private static Date _rootNotAfter;

    private static ValidityDates _rootValidityDates;

    private static String _userSubjectCn;

    private static String _userSubjectOrgUnit;

    private static String _userSubjectOrg;

    private static String _userSubjectLocality;

    private static String _userSubjectCountry;

    private static X509DistinguishedName _userSubjectDn;

    private static String _userIssuerCn;

    private static String _userIssuerOrgUnit;

    private static String _userIssuerOrg;

    private static String _userIssuerLocality;

    private static String _userIssuerCountry;

    private static int _userNumYearsValidity;

    private static X509DistinguishedName _userIssuerDn;

    private static Date _userNotBefore;

    private static Date _userNotAfter;

    private static ValidityDates _userValidityDates;

    private static AsymmetricService _asymmetricService;

    private static CertificatesService _certificatesService;

    private static CryptoX509Certificate _caCryptoCert;

    private static CryptoX509Certificate _signCryptoCert;

    private static CryptoX509Certificate _encryptCryptoCert;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());
        _target = new CrCertificateValidationServiceImpl();

        _asymmetricService = new AsymmetricService();

        _certificatesService = new CertificatesService();

        retrieveKeyPairs();

        retrieveInputParameters();

        // /////////////////////////////////////////////
        //
        // Root certificate data
        //
        // /////////////////////////////////////////////

        createRootDistinguishedNames();

        createRootValidityDates();

        RootCertificateData rootCertificateData = new RootCertificateData();
        rootCertificateData.setSubjectPublicKey(_rootKeyPair.getPublic());
        rootCertificateData.setSubjectDn(_rootSubjectDn);
        rootCertificateData.setValidityDates(_rootValidityDates);

        _caCryptoCert = (CryptoX509Certificate) _certificatesService
            .createRootAuthorityX509Certificate(rootCertificateData, _rootKeyPair.getPrivate());

        // /////////////////////////////////////////////
        //
        // User certificate data
        //
        // /////////////////////////////////////////////

        createUserDistinguishedNames();

        createUserValidityDates();

        CertificateData userCertificate = new CertificateData();
        userCertificate.setSubjectPublicKey(_userKeyPair.getPublic());
        userCertificate.setIssuerDn(_userIssuerDn);
        userCertificate.setSubjectDn(_userSubjectDn);
        userCertificate.setValidityDates(_userValidityDates);

        _signCryptoCert = (CryptoX509Certificate) _certificatesService.createSignX509Certificate(userCertificate,
            _rootKeyPair.getPrivate());

        _encryptCryptoCert = (CryptoX509Certificate) _certificatesService
            .createEncryptionX509Certificate(userCertificate, _rootKeyPair.getPrivate());
    }

    // /////////////////////////////////////////////
    //
    // Tests using new validator
    //
    // /////////////////////////////////////////////

    @Test
    public void whenValidateUserSignCertificateThenOK()
            throws GeneralCryptoLibException, CryptographicOperationException {

        CertificateValidationResult validationResult =
            _target.validateCertificate(_signCryptoCert.getCertificate(), _caCryptoCert.getCertificate());

        assertTrue(validationResult.isValid() && validationResult.getValidationErrorMessages().isEmpty());
    }

    @Test
    public void whenValidateUserEncryptionCertificateThenOK()
            throws GeneralCryptoLibException, CryptographicOperationException {

        CertificateValidationResult validationResult =
            _target.validateCertificate(_encryptCryptoCert.getCertificate(), _caCryptoCert.getCertificate());

        assertTrue(validationResult.isValid() && validationResult.getValidationErrorMessages().isEmpty());
    }

    @Test
    public void whenValidateRootCertificateUsingUserCertificateMethodThenExpectedFailures()
            throws GeneralCryptoLibException, CryptographicOperationException {

        CertificateValidationResult validationResult =
            _target.validateCertificate(_caCryptoCert.getCertificate(), _caCryptoCert.getCertificate());

        assertTrue(!validationResult.isValid() && validationResult.getValidationErrorMessages().size() == 1);
    }

    @Test
    public void whenValidateRootCertificateThenOK() throws CryptographicOperationException {

        X509CertificateValidationResult validationResult =
            _target.validateRootCertificate(_caCryptoCert.getCertificate());

        assertTrue(validationResult.isValidated() && validationResult.getFailedValidationTypes().isEmpty());
    }

    @Test
    public void whenValidateSignCertificateAsRootCertificateThenExpected()
            throws GeneralCryptoLibException, CryptographicOperationException {

        X509CertificateValidationResult validationResult =
            _target.validateRootCertificate(_signCryptoCert.getCertificate());

        assertTrue(!validationResult.isValidated() && validationResult.getFailedValidationTypes().size() == 2);
        assertTrue(validationResult.getFailedValidationTypes().contains(X509CertificateValidationType.SIGNATURE));
        assertTrue(validationResult.getFailedValidationTypes().contains(X509CertificateValidationType.KEY_TYPE));
    }

    // /////////////////////////////////////////////
    //
    // Tests using cryptolib validator
    //
    // /////////////////////////////////////////////

    @Test
    public void checkCertAuthCertificateValidityWithCorrectData() throws GeneralCryptoLibException {

        Date date = new Date(System.currentTimeMillis());

        X509CertificateValidationData validationData = new X509CertificateValidationData.Builder().addDate(date)
            .addSubjectDn(_rootSubjectDn).addIssuerDn(_rootSubjectDn)
            .addKeyType(X509CertificateType.CERTIFICATE_AUTHORITY).addCaPublicKey(_rootKeyPair.getPublic()).build();

        X509CertificateValidator validator = new X509CertificateValidator(_caCryptoCert, validationData,
            X509CertificateValidationType.SUBJECT, X509CertificateValidationType.ISSUER,
            X509CertificateValidationType.KEY_TYPE, X509CertificateValidationType.SIGNATURE);

        X509CertificateValidationResult validationResult = validator.validate();

        assert (validationResult.isValidated() && validationResult.getFailedValidationTypes().isEmpty());
    }

    @Test
    public void checkSignCertificateValidityWithCorrectData() throws GeneralCryptoLibException {

        Date date = new Date(System.currentTimeMillis());

        X509CertificateValidationData validationData = new X509CertificateValidationData.Builder().addDate(date)
            .addSubjectDn(_userSubjectDn).addIssuerDn(_userIssuerDn).addKeyType(X509CertificateType.SIGN)
            .addCaPublicKey(_rootKeyPair.getPublic()).build();

        X509CertificateValidator validator = new X509CertificateValidator(_signCryptoCert, validationData,
            X509CertificateValidationType.SUBJECT, X509CertificateValidationType.ISSUER,
            X509CertificateValidationType.KEY_TYPE, X509CertificateValidationType.SIGNATURE);

        X509CertificateValidationResult validationResult = validator.validate();

        assert (validationResult.isValidated() && validationResult.getFailedValidationTypes().isEmpty());
    }

    @Test
    public void checkEncryptCertificateValidityWithCorrectData() throws GeneralCryptoLibException {

        Date date = new Date(System.currentTimeMillis());

        X509CertificateValidationData validationData = new X509CertificateValidationData.Builder().addDate(date)
            .addSubjectDn(_userSubjectDn).addIssuerDn(_userIssuerDn).addKeyType(X509CertificateType.ENCRYPT)
            .addCaPublicKey(_rootKeyPair.getPublic()).build();

        X509CertificateValidator validator = new X509CertificateValidator(_encryptCryptoCert, validationData,
            X509CertificateValidationType.SUBJECT, X509CertificateValidationType.ISSUER,
            X509CertificateValidationType.KEY_TYPE, X509CertificateValidationType.SIGNATURE);

        X509CertificateValidationResult validationResult = validator.validate();

        assert (validationResult.isValidated() && validationResult.getFailedValidationTypes().isEmpty());
    }

    @Test
    public void checkCertAuthCertificateValidityWithIncorrectData() throws GeneralCryptoLibException {

        Date date = new Date(System.currentTimeMillis());

        X509CertificateValidationData validationData = new X509CertificateValidationData.Builder().addDate(date)
            .addSubjectDn(_rootSubjectDn).addIssuerDn(_rootSubjectDn).addKeyType(X509CertificateType.ENCRYPT)
            .addCaPublicKey(_rootKeyPair.getPublic()).build();

        X509CertificateValidator validator = new X509CertificateValidator(_caCryptoCert, validationData,
            X509CertificateValidationType.SUBJECT, X509CertificateValidationType.ISSUER,
            X509CertificateValidationType.KEY_TYPE, X509CertificateValidationType.SIGNATURE);

        X509CertificateValidationResult validationResult = validator.validate();

        assert (!validationResult.isValidated() && validationResult.getFailedValidationTypes().size() == 1
            && validationResult.getFailedValidationTypes().get(0).equals(X509CertificateValidationType.KEY_TYPE));
    }

    @Test
    public void checkSignCertificateValidityWithIncorrectData() throws GeneralCryptoLibException {

        Date date = new Date(System.currentTimeMillis());

        X509CertificateValidationData validationData = new X509CertificateValidationData.Builder().addDate(date)
            .addSubjectDn(_userSubjectDn).addIssuerDn(_userIssuerDn).addKeyType(X509CertificateType.SIGN)
            .addCaPublicKey(_userKeyPair.getPublic()).build();

        X509CertificateValidator validator = new X509CertificateValidator(_signCryptoCert, validationData,
            X509CertificateValidationType.SUBJECT, X509CertificateValidationType.ISSUER,
            X509CertificateValidationType.KEY_TYPE, X509CertificateValidationType.SIGNATURE);

        X509CertificateValidationResult validationResult = validator.validate();

        assert (!validationResult.isValidated() && validationResult.getFailedValidationTypes().size() == 1
            && validationResult.getFailedValidationTypes().get(0).equals(X509CertificateValidationType.SIGNATURE));
    }

    @Test
    public void checkEncryptCertificateValidityWithIncorrectData() throws GeneralCryptoLibException {

        Date date = new Date(System.currentTimeMillis());

        X509CertificateValidationData validationData = new X509CertificateValidationData.Builder().addDate(date)
            .addSubjectDn(_userSubjectDn).addIssuerDn(_userSubjectDn).addKeyType(X509CertificateType.ENCRYPT)
            .addCaPublicKey(_rootKeyPair.getPublic()).build();

        X509CertificateValidator validator = new X509CertificateValidator(_encryptCryptoCert, validationData,
            X509CertificateValidationType.SUBJECT, X509CertificateValidationType.ISSUER,
            X509CertificateValidationType.KEY_TYPE, X509CertificateValidationType.SIGNATURE);

        X509CertificateValidationResult validationResult = validator.validate();

        assert (!validationResult.isValidated() && validationResult.getFailedValidationTypes().size() == 1
            && validationResult.getFailedValidationTypes().get(0).equals(X509CertificateValidationType.ISSUER));
    }

    private static void retrieveKeyPairs() throws GeneralCryptoLibException {

        _rootKeyPair = _asymmetricService.getKeyPairForSigning();
        _userKeyPair = _asymmetricService.getKeyPairForSigning();
    }

    private static void retrieveInputParameters() {

        // Retrieve root certificate properties.
        Properties properties = loadProperties(
            X509CertificateTestConstants.ROOT_CERTIFICATE_PROPERTIES_FILE_PATH);
        _rootSubjectCn = properties.getProperty(
            X509CertificateTestConstants.SUBJECT_COMMON_NAME_PROPERTY_NAME);
        _rootSubjectOrgUnit = properties.getProperty(
            X509CertificateTestConstants.SUBJECT_ORGANIZATIONAL_UNIT_PROPERTY_NAME);
        _rootSubjectOrg = properties.getProperty(
            X509CertificateTestConstants.SUBJECT_ORGANIZATION_PROPERTY_NAME);
        _rootSubjectLocality = properties.getProperty(
            X509CertificateTestConstants.SUBJECT_LOCALITY_PROPERTY_NAME);
        _rootSubjectCountry = properties.getProperty(
            X509CertificateTestConstants.SUBJECT_COUNTRY_PROPERTY_NAME);
        _rootNumYearsValidity = Integer.parseInt(properties.getProperty(
            X509CertificateTestConstants.NUMBER_YEARS_VALIDITY_PROPERTY_NAME));

        // Retrieve user certificate properties.
        properties = loadProperties(
            X509CertificateTestConstants.USER_CERTIFICATE_PROPERTIES_FILE_PATH);
        _userSubjectCn = properties.getProperty(
            X509CertificateTestConstants.SUBJECT_COMMON_NAME_PROPERTY_NAME);
        _userSubjectOrgUnit = properties.getProperty(
            X509CertificateTestConstants.SUBJECT_ORGANIZATIONAL_UNIT_PROPERTY_NAME);
        _userSubjectOrg = properties.getProperty(
            X509CertificateTestConstants.SUBJECT_ORGANIZATION_PROPERTY_NAME);
        _userSubjectLocality = properties.getProperty(
            X509CertificateTestConstants.SUBJECT_LOCALITY_PROPERTY_NAME);
        _userSubjectCountry = properties.getProperty(
            X509CertificateTestConstants.SUBJECT_COUNTRY_PROPERTY_NAME);
        _userIssuerCn = properties.getProperty(
            X509CertificateTestConstants.ISSUER_COMMON_NAME_PROPERTY_NAME);
        _userIssuerOrgUnit = properties.getProperty(
            X509CertificateTestConstants.ISSUER_ORGANIZATIONAL_UNIT_PROPERTY_NAME);
        _userIssuerOrg = properties.getProperty(
            X509CertificateTestConstants.ISSUER_ORGANIZATION_PROPERTY_NAME);
        _userIssuerLocality = properties.getProperty(
            X509CertificateTestConstants.ISSUER_LOCALITY_PROPERTY_NAME);
        _userIssuerCountry = properties.getProperty(
            X509CertificateTestConstants.ISSUER_COUNTRY_PROPERTY_NAME);
        _userNumYearsValidity = Integer.parseInt(properties.getProperty(
            X509CertificateTestConstants.NUMBER_YEARS_VALIDITY_PROPERTY_NAME));
    }
    
    private static Properties loadProperties(String resource) {
        Properties properties = new Properties();
        try (InputStream stream = CertificateValidationServiceTest.class.getClassLoader().getResourceAsStream(resource);
                Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
            properties.load(reader);
        } catch (IOException e) {
            throw new IllegalStateException("Failed to load properties.", e);
        }
        return properties;
    }

    private static void createRootValidityDates() throws GeneralCryptoLibException {

        _rootNotBefore = new Date(System.currentTimeMillis());
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.YEAR, _rootNumYearsValidity);
        _rootNotAfter = calendar.getTime();
        _rootValidityDates = new ValidityDates(_rootNotBefore, _rootNotAfter);
    }

    private static void createRootDistinguishedNames() throws GeneralCryptoLibException {

        _rootSubjectDn = new X509DistinguishedName.Builder(_rootSubjectCn, _rootSubjectCountry)
            .addOrganizationalUnit(_rootSubjectOrgUnit).addOrganization(_rootSubjectOrg)
            .addLocality(_rootSubjectLocality).build();
    }

    private static void createUserDistinguishedNames() throws GeneralCryptoLibException {

        _userSubjectDn = new X509DistinguishedName.Builder(_userSubjectCn, _userSubjectCountry)
            .addOrganizationalUnit(_userSubjectOrgUnit).addOrganization(_userSubjectOrg)
            .addLocality(_userSubjectLocality).build();
        _userIssuerDn = new X509DistinguishedName.Builder(_userIssuerCn, _userIssuerCountry)
            .addOrganizationalUnit(_userIssuerOrgUnit).addOrganization(_userIssuerOrg).addLocality(_userIssuerLocality)
            .build();
    }

    private static void createUserValidityDates() throws GeneralCryptoLibException {

        Calendar calendar;
        _userNotBefore = new Date(System.currentTimeMillis());
        calendar = Calendar.getInstance();
        calendar.add(Calendar.YEAR, _userNumYearsValidity);
        _userNotAfter = calendar.getTime();
        _userValidityDates = new ValidityDates(_userNotBefore, _userNotAfter);
    }
}
