package com.mobify.astro.security;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;

import com.mobify.astro.ActivityTestBase;
import com.mobify.astro.helpers.RandomStringGenerator;

import org.junit.Before;
import org.junit.Test;

import java.security.Key;
import java.security.KeyStore;
import java.util.Locale;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;

public class CryptoManagerTest  extends ActivityTestBase {
    final static String SYMMETRIC_KEY_FILE_KEY = "com.mobify.astro.SYMMETRIC_KEY";
    final static String SHARE_PREF_SYMMETRIC_KEY = "sharePrefSymmetricKey";
    final static String KEYSTORE = "AndroidKeyStore";

    final static String testKey = "testKey";
    final static String testString = RandomStringGenerator.generate(32);
    final static String largeString = RandomStringGenerator.generate(512);
    final static int NEW_API_SUPPORT = 23;
    final static int currentAPIVersion = Build.VERSION.SDK_INT;

    KeyStore keystore;
    String asymmetricKeystoreAlias;
    SharedPreferences symmetricKeySharedPref;
    boolean isCallbackCalled;

    CryptoManager cryptoManager;
    CryptoManager.CryptoManagerListener cryptoManagerListener = new CryptoManager.CryptoManagerListener() {
        @Override
        public void onKeysDestroy() {
            isCallbackCalled = true;
        }
    };

    @Before
    public void setup() throws Throwable {
        asymmetricKeystoreAlias = RsaAsymmetricCryptosystem.keystoreAlias(getActivity());

        keystore = KeyStore.getInstance(KEYSTORE);
        keystore.load(null);
        keystore.deleteEntry(asymmetricKeystoreAlias);

        symmetricKeySharedPref = getActivity().getSharedPreferences(SYMMETRIC_KEY_FILE_KEY, Context.MODE_PRIVATE);
        symmetricKeySharedPref.edit().clear().apply();

        isCallbackCalled = false;
        cryptoManager = new CryptoManager(getActivity(), cryptoManagerListener);
    }

    Key getAsymmetricKey() {
        try {
            Key privateKey;
            if (currentAPIVersion < NEW_API_SUPPORT) {
                KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keystore.getEntry(asymmetricKeystoreAlias, null);
                privateKey = privateKeyEntry.getPrivateKey();
            } else {
                privateKey = keystore.getKey(asymmetricKeystoreAlias, null);
            }

            return privateKey;
        } catch (Exception e) {
            return null;
        }
    }

    int getNumberOfKeys() {
        Map<String, ?> keys = symmetricKeySharedPref.getAll();
        return keys.size();
    }

    String getSymmetricKey() {
        return symmetricKeySharedPref.getString(SHARE_PREF_SYMMETRIC_KEY, null);
    }

    @Test
    public void testInitialize() throws Exception {
        assertTrue(keystore.containsAlias(asymmetricKeystoreAlias));
        assertNotNull(getSymmetricKey());

        assertEquals(CryptoManager.isAesGcmAvailable() ? 1 : 2, getNumberOfKeys());
    }

    @Test
    public void testInitializeWithLocale() throws Exception {
        keystore.deleteEntry(asymmetricKeystoreAlias);

        Locale.setDefault(new Locale("ar", "sa"));

        new CryptoManager(getActivity(), cryptoManagerListener);

        assertTrue(keystore.containsAlias(asymmetricKeystoreAlias));
        assertNotNull(getSymmetricKey());
        assertEquals(CryptoManager.isAesGcmAvailable() ? 1 : 2, getNumberOfKeys());
        assertEquals("ar", Locale.getDefault().getLanguage());
        assertEquals("SA", Locale.getDefault().getCountry());
    }

    @Test
    public void testReinitializingDoesntDeleteExistingKeys() throws Exception{
        Key currentEncodeKey = getAsymmetricKey();
        String currentSymmetricKey = getSymmetricKey();

        new CryptoManager(getActivity(), cryptoManagerListener);

        assertEquals(currentEncodeKey, getAsymmetricKey());
        assertEquals(currentSymmetricKey, getSymmetricKey());
    }

    @Test
    public void testCleanReInitialize() throws Exception {
        Key currentEncodeKey = getAsymmetricKey();
        String currentSymmetricKey = getSymmetricKey();

        keystore.deleteEntry(asymmetricKeystoreAlias);

        new CryptoManager(getActivity(), cryptoManagerListener);

        assertNotSame(currentEncodeKey, getAsymmetricKey());
        assertNotSame(currentSymmetricKey, getSymmetricKey());
    }

    @Test
    public void testListenerCallbackIsCalled() throws Exception {
        keystore.deleteEntry(asymmetricKeystoreAlias);
        isCallbackCalled = false;

        new CryptoManager(getActivity(), cryptoManagerListener);

        assertTrue(isCallbackCalled);
    }

    @Test
    public void testKeystoreInitializationRetry() throws Exception {
        symmetricKeySharedPref.edit().clear().apply();
        assertEquals("", cryptoManager.encrypt(testKey, testString));

        String encryptedString = cryptoManager.encrypt(testKey, testString);

        assertNotSame("", encryptedString);
        assertNotSame(testString, encryptedString);
    }

    @Test
    public void testKeystoreInitializationSecondRetry() throws Exception {
        symmetricKeySharedPref.edit().clear().apply();
        assertEquals("", cryptoManager.encrypt(testKey, testString));

        symmetricKeySharedPref.edit().clear().apply();
        assertEquals("", cryptoManager.encrypt(testKey, testString));

        String encryptedString = cryptoManager.encrypt(testKey, testString);

        assertNotSame("", encryptedString);
        assertNotSame(testString, encryptedString);
    }

    @Test
    public void testEncrypt() throws Exception {
        assertNotSame(testString, cryptoManager.encrypt(testKey, testString));
    }

    @Test
    public void testEncryptLargeValue() throws Exception {
        assertNotSame(largeString, cryptoManager.encrypt(testKey, largeString));
    }

    @Test
    public void testEncryptNull() throws Exception {
        assertEquals("", cryptoManager.encrypt(null, null));
    }

    @Test
    public void testEncryptNullKey() throws Exception {
        assertEquals("", cryptoManager.encrypt(null, testString));
    }

    @Test
    public void testEncryptNullValue() throws Exception {
        assertEquals("", cryptoManager.encrypt(testKey, null));
    }

    @Test
    public void testDecrypt() throws Exception {
        String encryptedTestString = cryptoManager.encrypt(testKey, testString);
        assertEquals(testString, cryptoManager.decrypt(testKey, encryptedTestString));
    }

    @Test
    public void testDecryptLargeValue() throws Exception {
        String encryptedLargeString = cryptoManager.encrypt(testKey, largeString);
        assertEquals(largeString, cryptoManager.decrypt(testKey, encryptedLargeString));
    }

    @Test
    public void testDecryptNull() throws Exception {
        assertEquals(null, cryptoManager.decrypt(null, null));
    }

    @Test
    public void testDecryptNullKey() throws Exception {
        assertEquals(null, cryptoManager.decrypt(null, testString));
    }

    @Test
    public void testDecryptNullValue() throws Exception {
        assertEquals(null, cryptoManager.decrypt(testKey, null));
    }
}

