/*
 * 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
 */
/**
 * $Id$
 *
 * @author riglesias
 * @date 4/11/16
 *
 *
 * All rights reserved.
 */
package com.scytl.product.ov.ea.services.domain.validation;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.time.ZonedDateTime;
import java.util.Base64;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.BeforeClass;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.certificates.CertificatesServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.certificates.service.CertificatesService;
import com.scytl.products.ov.commons.beans.domain.model.Information.VoterInformation;
import com.scytl.products.ov.commons.beans.domain.model.authentication.AuthenticationToken;
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.CertificateParametersLoader;
import com.scytl.products.ov.commons.crypto.configuration.ConfigurationInput;
import com.scytl.products.ov.commons.crypto.configuration.ConfigurationInputReader;
import com.scytl.products.ov.commons.crypto.configuration.CredentialProperties;
import com.scytl.products.ov.commons.crypto.configuration.ReplacementsHolder;
import com.scytl.products.ov.commons.crypto.configuration.X509CertificateGenerator;
import com.scytl.products.ov.commons.util.DateUtils;

/**
 * General class for creating cryptographic data to be tested
 */
public class ValidationTest {

    public static final String KEYS_CONFIG_JSON = "/keys_config.json";

    public static final String TOKEN_ID = "8f24520bc9e346cd951289b9676567f6";

    private static AsymmetricServiceAPI asymmetricService;

    protected static CertificatesServiceAPI certificatesService;

    protected final CertificateDataBuilder certificateDataBuilder = new CertificateDataBuilder();

    protected X509CertificateGenerator certificateGenerator =
        new X509CertificateGenerator(certificatesService, certificateDataBuilder);

    public static final String GENERIC_ID = "1";

    private static ConfigurationInput configurationInput;

    public static final String COUNTRY = "ES";

    public static final String SCYTL = "SCYTL";

    ReplacementsHolder holder = new ReplacementsHolder(GENERIC_ID, GENERIC_ID);

    @BeforeClass
    public static void init() throws URISyntaxException, IOException, GeneralCryptoLibException {
        Security.addProvider(new BouncyCastleProvider());

        asymmetricService = new AsymmetricService();

        certificatesService = new CertificatesService();

        ConfigurationInputReader reader = new ConfigurationInputReader();
        // Read keys_config.json from classpath
        URL url = ValidationTest.class.getResource(KEYS_CONFIG_JSON);
        configurationInput = reader.fromFileToJava(new File(url.toURI()));

    }
    
    @AfterClass
    public static void deinitialize() {
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    protected AuthTokenCryptoInfo getTokenKeys() throws IOException, GeneralCryptoLibException {
        final KeyPair keyPairForSigning = getKeyPairForSigning();
        final CredentialProperties authTokenSigner = configurationInput.getAuthTokenSigner();
        final CryptoAPIX509Certificate cryptoAPIX509Certificate =
            getAuthtokenCertificate(authTokenSigner, keyPairForSigning);
        AuthTokenCryptoInfo info = new AuthTokenCryptoInfo();
        info.setCertificate(cryptoAPIX509Certificate);
        info.setPrivateKey(keyPairForSigning.getPrivate());
        return info;
    }

    /**
     * Generates an AuthToken
     *
     * @return
     */
    protected AuthenticationToken generateToken(String tenantId, String electionEventId) {
        AuthenticationToken token = new AuthenticationToken();
        VoterInformation voterInformation = new VoterInformation();
        voterInformation.setBallotBoxId(GENERIC_ID);
        voterInformation.setBallotId(GENERIC_ID);
        voterInformation.setCredentialId(GENERIC_ID);
        voterInformation.setElectionEventId(electionEventId);
        voterInformation.setVerificationCardId(GENERIC_ID);
        voterInformation.setVerificationCardSetId(GENERIC_ID);
        voterInformation.setVotingCardId(GENERIC_ID);
        voterInformation.setTenantId(tenantId);
        voterInformation.setVotingCardSetId(GENERIC_ID);
        token.setVoterInformation(voterInformation);
        token.setId(TOKEN_ID);
        return token;
    }

    /**
     * Signs an authenticationToken
     *
     * @param privateKey
     * @param token
     * @throws GeneralCryptoLibException
     */
    protected void signToken(PrivateKey privateKey, AuthenticationToken token) throws GeneralCryptoLibException {

        token.setTimestamp(DateUtils.getTimestamp());

        final byte[] bytesToSign = StringUtils.join(token.getFieldsAsStringArray()).getBytes(StandardCharsets.UTF_8);
        final byte[] signature = asymmetricService.sign(privateKey, bytesToSign);

        token.setSignature(Base64.getEncoder().encodeToString(signature));

    }

    protected KeyPair getKeyPairForSigning() {
        return asymmetricService.getKeyPairForSigning();
    }

    private CryptoAPIX509Certificate getAuthtokenCertificate(CredentialProperties authTokenSignerProperties,
            KeyPair keyPair) throws IOException, GeneralCryptoLibException {

        ZonedDateTime now = ZonedDateTime.now();
        ZonedDateTime endDate = now.plusDays(3);

        final CertificateParameters certificateParameters =
            getCertificateParameters(authTokenSignerProperties, now, endDate, holder, keyPair);

        return createX509Certificate(certificateParameters, keyPair.getPublic(), keyPair.getPrivate());

    }

    protected CertificateParameters getCertificateParameters(final CredentialProperties credentialProperties,
            final ZonedDateTime startDate, final ZonedDateTime endDate, final ReplacementsHolder replacementsHolder,
            final KeyPair keyPair) throws IOException {
        final Properties props = new Properties();

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

        final CertificateParameters.Type type = credentialProperties.getCredentialType();
        final CertificateParametersLoader certificateParametersLoader =
            new CertificateParametersLoader(replacementsHolder);
        final CertificateParameters certificateParameters = certificateParametersLoader.load(props, type);
        certificateParameters.setUserNotBefore(startDate);
        certificateParameters.setUserNotAfter(endDate);
        return certificateParameters;
    }

    protected CryptoAPIX509Certificate createX509Certificate(final CertificateParameters certificateParameters,
            PublicKey publicKey, PrivateKey issuerKey) throws GeneralCryptoLibException {

        return certificateGenerator.generate(certificateParameters, publicKey, issuerKey);
    }

    protected CertificateParameters createCertificateParameters(String commonName) {

        ZonedDateTime now = ZonedDateTime.now();
        CertificateParameters params = new CertificateParameters();
        params.setType(CertificateParameters.Type.SIGN);
        params.setUserIssuerCn(commonName);
        params.setUserIssuerCountry(COUNTRY);
        params.setUserIssuerOrg(SCYTL);
        params.setUserIssuerOrgUnit(SCYTL);
        params.setUserNotAfter(now.plusYears(1));
        params.setUserNotBefore(now);
        params.setUserSubjectCn(commonName);
        params.setUserSubjectCountry(COUNTRY);
        params.setUserSubjectOrg(SCYTL);
        params.setUserSubjectOrgUnit(SCYTL);
        return params;
    }

}
