package com.mobify.astro.security;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * The SymmetricKeyService creates symmetric encryption keys for each shared preference key passed
 * into the constructor in the sharedPrefSymmetricKeys parameter.
 *
 * These encryption keys are stored in a shared preference file which is only accessible by the app.
 * Unfortunately if a phone is rooted then shared preference files are accessible on the device.
 *
 * Therefore it is necessary to encrypt the symmetric keys while they are being stored in the shared
 * preferences file. The only hardware backed encryption service in android is the Android Keystore.
 * It can currently only store asymmetric keys, but asymmetric keys can only encrypt data of a limited
 * length.
 *
 * Thus the symmetric keys which we use for encrypting/decrypting our stored data are
 * encrypted/decrypted using a asymmetric keypair which is stored in the Android Keystore.
 */
class SymmetricKeyService {
    private final static String TAG = SymmetricKeyService.class.getName();
    private final static String SYMMETRIC_KEY_FILE_KEY = "com.mobify.astro.SYMMETRIC_KEY";
    private final static String SYMMETRIC_KEY_GENERATION_ALGORITHM = "AES";
    private final static int SYMMETRIC_KEY_LENGTH_BITS = 256;

    private final AsymmetricCryptosystem asymmetricCryptosystem;
    private final ArrayList<String> sharedPrefSymmetricKeys;
    private final SharedPreferences symmetricKeySharedPref;
    private final SharedPreferences.Editor keyEditor;

    private boolean retry = true;

    private final SymmetricKeyServiceListener callback;

    interface SymmetricKeyServiceListener {
        void onKeysDestroy();
    }

    SymmetricKeyService(Context context, ArrayList<String> sharedPrefSymmetricKeys, SymmetricKeyServiceListener symmetricKeyServiceListener) throws Exception {
        this.asymmetricCryptosystem = new RsaAsymmetricCryptosystem(context);
        this.sharedPrefSymmetricKeys = sharedPrefSymmetricKeys;
        this.callback = symmetricKeyServiceListener;

        symmetricKeySharedPref = context.getSharedPreferences(SYMMETRIC_KEY_FILE_KEY, Context.MODE_PRIVATE);
        keyEditor = symmetricKeySharedPref.edit();

        initKeys();
    }

    SecretKey getSymmetricKey(String sharePrefSymmetricKey) throws Exception {
        String encryptedSecretKey = symmetricKeySharedPref.getString(sharePrefSymmetricKey, null);

        String decryptedSecretKey = asymmetricCryptosystem.decrypt(encryptedSecretKey);

        return new SecretKeySpec(Base64.decode(decryptedSecretKey, Base64.DEFAULT), SYMMETRIC_KEY_GENERATION_ALGORITHM);
    }

    void resetKeys(Exception e) throws Exception {
        asymmetricCryptosystem.clear();
        clearSymmetricKeys();

        if (retry) {
            retry = false;
            initKeys();
        } else {
            retry = true;
            throw e;
        }
    }

    private void initKeys() throws Exception {
        try {
            if (!asymmetricCryptosystem.isInitialized()) {
                asymmetricCryptosystem.init();

                storeSymmetricKeys();
            } else if (!hasSymmetricKeys()) {
                storeSymmetricKeys();
            }

            retry = true;
        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
            resetKeys(e);
        }
    }

    private boolean hasSymmetricKeys() {
        boolean hasSymmetricKeys = true;

        for (String sharedPrefKey: sharedPrefSymmetricKeys) {
            hasSymmetricKeys = hasSymmetricKeys && hasSymmetricKey(sharedPrefKey);
        }

        return hasSymmetricKeys;
    }

    private boolean hasSymmetricKey(String sharePrefSymmetricKey) {
        return symmetricKeySharedPref.getString(sharePrefSymmetricKey, null) != null;
    }

    private void clearSymmetricKeys() {
        keyEditor.clear().apply();
        callback.onKeysDestroy();
    }

    private void storeSymmetricKeys() throws Exception {
        clearSymmetricKeys();

        for (String sharedPrefKey: sharedPrefSymmetricKeys) {
            storeSymmetricKey(sharedPrefKey);
        }
    }

    private void storeSymmetricKey(String sharePrefSymmetricKey) throws Exception {
        // Generate and store our symmetric key encrypted with our asymmetric key
        String encodedSymmetricKey = Base64.encodeToString(generateSymmetricKey().getEncoded(), Base64.DEFAULT);
        String encryptedSymmetricKey = asymmetricCryptosystem.encrypt(encodedSymmetricKey);

        keyEditor.putString(sharePrefSymmetricKey, encryptedSymmetricKey);
        keyEditor.apply();
    }

    private SecretKey generateSymmetricKey() throws NoSuchAlgorithmException {
        // We are not seeding secure random because the only safe way to do that
        // would require the end user to enter a password
        // SecureRandom will automatically seed from system entropy
        SecureRandom random = new SecureRandom();

        KeyGenerator keyGenerator = KeyGenerator.getInstance(SYMMETRIC_KEY_GENERATION_ALGORITHM);
        keyGenerator.init(SYMMETRIC_KEY_LENGTH_BITS, random);

        return keyGenerator.generateKey();
    }
}
