/*
 * 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.asymmetric.keypair.factory;

import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;

import javax.crypto.NoSuchPaddingException;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.asymmetric.keypair.configuration.ConfigEncryptionKeyPairAlgorithmAndSpec;
import com.scytl.cryptolib.asymmetric.keypair.configuration.ConfigSigningKeyPairAlgorithmAndSpec;
import com.scytl.cryptolib.asymmetric.keypair.configuration.KeyPairPolicy;
import com.scytl.cryptolib.commons.utils.system.OperatingSystem;
import com.scytl.cryptolib.primitives.securerandom.configuration.ConfigSecureRandomAlgorithmAndProvider;

import mockit.Deencapsulation;
import mockit.Expectations;
import mockit.Injectable;
import mockit.Mock;
import mockit.MockUp;

/**
 * Tests of KeyPairFactory.
 */
public class KeyPairGeneratorFactoryTest {

    private static KeyPairPolicy _policy;

    private static KeyPairGeneratorFactory _keyPairFactory;

    private static OperatingSystem _os;

    @BeforeClass
    public static void setUp() {

        Security.addProvider(new BouncyCastleProvider());

        _policy = getPolicy();

        _keyPairFactory = new KeyPairGeneratorFactory(_policy);
        _os = OperatingSystem.current();
    }

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

    @After
    public void revertSystemHelper() {
        Deencapsulation.setField(OperatingSystem.class, "current",
            _os);
    }

    @Test
    public void givenKeyPairGeneratorFactoryWhenCreateEncryptionKeyPairThenExpectedValues()
            throws NoSuchAlgorithmException, NoSuchPaddingException {

        CryptoKeyPairGenerator keyPairGenerator =
            _keyPairFactory.createEncryption();

        KeyPair keyPair = keyPairGenerator.genKeyPair();

        assertEncryptionKeyPairHasExpectedValues(keyPair);
    }

    @Test
    public void givenKeyPairGeneratorFactoryWhenCreateSigningKeyPairThenExpectedValues()
            throws NoSuchAlgorithmException, NoSuchPaddingException {

        CryptoKeyPairGenerator keyPairGenerator =
            _keyPairFactory.createSigning();

        KeyPair keyPair = keyPairGenerator.genKeyPair();

        assertSigningKeyPairHasExpectedValues(keyPair);
    }

    @Test(expected = CryptoLibException.class)
    public void givenPolicyWithBadAlgorithmWhenCreateSigningKeyPairThenException()
            throws NoSuchAlgorithmException, NoSuchPaddingException {

        new MockUp<ConfigSigningKeyPairAlgorithmAndSpec>() {
            @Mock
            public String getAlgorithm() {

                return "BadAlgorithm";
            }
        };

        KeyPairGeneratorFactory keyPairFactory =
            new KeyPairGeneratorFactory(getPolicy());

        keyPairFactory.createSigning();
    }

    @Test(expected = CryptoLibException.class)
    public void givenPolicyWithBadAlgorithmWhenCreateEncryptionKeyPairThenException()
            throws NoSuchAlgorithmException, NoSuchPaddingException {

        new MockUp<ConfigSecureRandomAlgorithmAndProvider>() {
            @Mock
            public String getAlgorithm() {

                return "BadAlgorithm";
            }
        };

        KeyPairGeneratorFactory keyPairFactory =
            new KeyPairGeneratorFactory(getPolicy());

        keyPairFactory.createEncryption();
    }

    @Test(expected = CryptoLibException.class)
    public void givenMockedLinuxOSAndPolicyWithWindowsSecureRandomPropertyWhenCheckOsThenException(
            @Injectable final KeyPairPolicy policy) {

        Deencapsulation.setField(OperatingSystem.class, "current",
            OperatingSystem.LINUX);

        new Expectations() {
            {
                policy.getSecureRandomAlgorithmAndProvider();
                result =
                    ConfigSecureRandomAlgorithmAndProvider.PRNG_SUN_MSCAPI;
                ;
            }
        };

        KeyPairGeneratorFactory keyPairFactory =
            new KeyPairGeneratorFactory(policy);

        Deencapsulation.invoke(keyPairFactory, "checkOS");
    }

    @Test(expected = CryptoLibException.class)
    public void givenMockedWindowsOsAndPolicyWithLinuxSecureRandomPropertyWhenCheckOsThenException(
            @Injectable final KeyPairPolicy policy) {

        Deencapsulation.setField(OperatingSystem.class, "current",
            OperatingSystem.WINDOWS);

        new Expectations() {
            {
                policy.getSecureRandomAlgorithmAndProvider();
                result =
                    ConfigSecureRandomAlgorithmAndProvider.NATIVE_PRNG_SUN;
            }
        };

        KeyPairGeneratorFactory keyPairFactory =
            new KeyPairGeneratorFactory(policy);

        Deencapsulation.invoke(keyPairFactory, "checkOS");
    }

    private void assertEncryptionKeyPairHasExpectedValues(
            final KeyPair keyPair) {

        assertPublicKeyHasExpectedAlgorithmAndFormat(
            _policy.getSigningKeyPairAlgorithmAndSpec().getAlgorithm(),
            keyPair.getPublic());

        assertPrivateKeyHasExpectedAlgorithmAndFormat(
            _policy.getSigningKeyPairAlgorithmAndSpec().getAlgorithm(),
            keyPair.getPrivate());

    }

    private void assertSigningKeyPairHasExpectedValues(
            final KeyPair keyPair) {

        assertPublicKeyHasExpectedAlgorithmAndFormat(
            _policy.getSigningKeyPairAlgorithmAndSpec().getAlgorithm(),
            keyPair.getPublic());

        assertPrivateKeyHasExpectedAlgorithmAndFormat(
            _policy.getSigningKeyPairAlgorithmAndSpec().getAlgorithm(),
            keyPair.getPrivate());
    }

    private void assertPublicKeyHasExpectedAlgorithmAndFormat(
            final String expectedAlgorithm, final PublicKey publicKey) {

        Assert.assertEquals(expectedAlgorithm, publicKey.getAlgorithm());
        Assert.assertEquals("X.509", publicKey.getFormat());
    }

    private void assertPrivateKeyHasExpectedAlgorithmAndFormat(
            final String expectedAlgorithm, final PrivateKey privateKey) {

        Assert.assertEquals(expectedAlgorithm, privateKey.getAlgorithm());
        Assert.assertEquals("PKCS#8", privateKey.getFormat());
    }

    private static KeyPairPolicy getPolicy() {
        return new KeyPairPolicy() {

            @Override
            public ConfigSigningKeyPairAlgorithmAndSpec getSigningKeyPairAlgorithmAndSpec() {
                return ConfigSigningKeyPairAlgorithmAndSpec.RSA_2048_F4_SUN_RSA_SIGN;
            }

            @Override
            public ConfigSecureRandomAlgorithmAndProvider getSecureRandomAlgorithmAndProvider() {
                return getOsDependentSecureRandomAlgorithmAndProvider();
            }

            @Override
            public ConfigEncryptionKeyPairAlgorithmAndSpec getEncryptingKeyPairAlgorithmAndSpec() {
                return ConfigEncryptionKeyPairAlgorithmAndSpec.RSA_2048_F4_BC;
            }
        };
    }

    private static ConfigSecureRandomAlgorithmAndProvider getOsDependentSecureRandomAlgorithmAndProvider() {

        switch (OperatingSystem.current()) {

        case WINDOWS:
            return ConfigSecureRandomAlgorithmAndProvider.PRNG_SUN_MSCAPI;
        case LINUX:
            return ConfigSecureRandomAlgorithmAndProvider.NATIVE_PRNG_SUN;
        default:
            throw new CryptoLibException("OS not supported");
        }
    };
}
