package com.beem.project.beem.utils;

import android.annotation.TargetApi;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;

import com.google.android.apps.iosched.util.LogUtils;

import org.jivesoftware.smack.util.Base64;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;

import static com.google.android.apps.iosched.util.LogUtils.LOGD;
import static com.google.android.apps.iosched.util.LogUtils.LOGW;

public class EncryptionManager {

    private static final String TAG = LogUtils.makeLogTag(EncryptionManager.class);
    private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
    private static final String CIPHER_TRANSFORMATION = String.format("%s/%s/%s",
            KeyProperties.KEY_ALGORITHM_AES, KeyProperties.BLOCK_MODE_CBC,
            KeyProperties.ENCRYPTION_PADDING_PKCS7);
    private boolean isEncryptionAvailable;
    private KeyStore keystore;
    private KeyGenerator keyGenerator;
    private Cipher aesCipher;
    private byte[] latestEncryptionIv;

    public EncryptionManager() {

        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                keystore = KeyStore.getInstance(ANDROID_KEY_STORE);
                keystore.load(null);
                aesCipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
                keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
                isEncryptionAvailable = true;
            } else {
                isEncryptionAvailable = false;
            }
        } catch (NoSuchPaddingException | KeyStoreException | IOException | NoSuchProviderException | NoSuchAlgorithmException | CertificateException e) {
            LOGW(TAG, "Unable to load AndroidKeyStore", e);
            isEncryptionAvailable = false;
        }
    }

    public boolean isEncryptionAvailable() {
        return isEncryptionAvailable;
    }

    public boolean hasEncryptionKey(String alias) {
        if (!isEncryptionAvailable) {
            return false;
        }
        try {
            return keystore.getKey(alias, null) != null;
        } catch (GeneralSecurityException e) {
            LOGD(TAG, "Unable to get key", e);
            return false;
        }
    }

    @TargetApi(Build.VERSION_CODES.M)
    public boolean generateEncryptionKey(String alias) {
        if (!isEncryptionAvailable)
            return false;
        KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                .setUserAuthenticationRequired(false)
                .build();
        try {
            keyGenerator.init(spec);
            return keyGenerator.generateKey() != null;
        } catch (InvalidAlgorithmParameterException e) {
            LOGW(TAG, "Unable to generate key", e);
            return false;
        }
    }

    public String encryptString(String cleartext, String keyAlias) {
        if (!isEncryptionAvailable())
            return null;
        try {
            Key key = keystore.getKey(keyAlias, null);
            aesCipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] encrypted = aesCipher.doFinal(cleartext.getBytes());
            latestEncryptionIv = aesCipher.getIV();
            return Base64.encodeBytes(encrypted);
        } catch (GeneralSecurityException e) {
            LOGW(TAG, "Unable to encrypt text", e);
            return null;
        }
    }

    public byte[] getLatestEncryptionIv() {
        return latestEncryptionIv;
    }

    public String decryptString(String password, String keyAlias, byte[] encryptionIv) {
        if (!isEncryptionAvailable())
            return null;
        try {
            Key key = keystore.getKey(keyAlias, null);
            byte[] passwordByte = Base64.decode(password);
            aesCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(encryptionIv));
            byte[] clear = aesCipher.doFinal(passwordByte);
            return new String(clear);
        } catch (GeneralSecurityException e) {
            LOGW(TAG, "Unable to decrypt text", e);
            return null;
        }
    }
}
