package io.trustbuilder.mfa.android.demoapp.config;

import static android.content.ContentValues.TAG;
import static android.content.Context.MODE_PRIVATE;

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

import com.google.gson.Gson;

import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.crypto.Cipher;

import io.trustbuilder.mfa.android.demoapp.R;
import io.trustbuilder.mfa.android.demoapp.security.CryptographyException;
import io.trustbuilder.mfa.android.demoapp.security.CryptographyManager;
import io.trustbuilder.mfa.android.demoapp.security.EncryptedData;
import io.trustbuilder.mfa.lib.application.FileUtils;
import io.trustbuilder.mfa.lib.domain.ISecureTokenStorage;
import io.trustbuilder.mfa.lib.domain.SecureToken;
import io.trustbuilder.mfa.lib.generator.service.GsonHelper;

public class ConfigSecureTokenStorage implements ISecureTokenStorage, Serializable {
    private static ConfigSecureTokenStorage instance;
    private final Context context;
    private final CryptographyManager cryptographyManager;
    private final SharedPreferences sharedPreferences;
    private static final String DATA_FILE_NAME = "data";
    private static final String SHARED_PREFERENCE_KEY = "demo_android";
    private static final String SHARED_PREFERENCE_DATA_KEY_IV = "encrypted_data_iv";
    private static final String PREFIX = "tb_tkn_";

    private ConfigSecureTokenStorage(Activity activity) {
        context = activity;
        this.cryptographyManager = CryptographyManager.getInstance();
        this.sharedPreferences = activity.getSharedPreferences(SHARED_PREFERENCE_KEY, MODE_PRIVATE);
    }

    public static ConfigSecureTokenStorage getInstance(Activity activity) {
        if (instance == null) {
            synchronized (ConfigSecureTokenStorage.class) {
                if (instance == null) {
                    instance = new ConfigSecureTokenStorage(activity);
                }
            }
        }
        return instance;
    }

    @Override
    public void save(SecureToken host)  {
        String filePath = new File(context.getFilesDir(), getSecureTokenFileName(host.getAlias())).getAbsolutePath();
        Gson gson = GsonHelper.customGson;
        String json = gson.toJson(host);

        Cipher cipher = this.cryptographyManager.getInitializedCipherForEncryption(
                context.getString(R.string.inwebo_data_secret_key),
                false
        );
        SharedPreferences.Editor editor = this.sharedPreferences.edit();
        EncryptedData encryptedData;
        try {
            encryptedData = this.cryptographyManager.encryptData(json, cipher);
        } catch (CryptographyException e) {
            throw new RuntimeException("Error while encrypting JSON data", e);
        }
        editor.putString(
                getInitialisationVectorKey(host.getAlias()),
                Base64.encodeToString(encryptedData.getInitialisationVector(), Base64.NO_WRAP)
        );
        editor.apply();
        FileUtils.writeData(
                filePath,
                Base64.encodeToString(encryptedData.getCipherText(), Base64.NO_WRAP)
        );
    }

    @Override
    public SecureToken get(String tknAlias) {
        String filePath = new File(context.getFilesDir(), getSecureTokenFileName(tknAlias)).getAbsolutePath();
        byte[] encryptedDataIv = Base64.decode(
                this.sharedPreferences.getString(getInitialisationVectorKey(tknAlias), ""),
                Base64.NO_WRAP
        );
        String encryptedDataString = FileUtils.readFile(filePath);
        if (encryptedDataString == null) {
            return null;
        }
        byte[] encryptedData = Base64.decode(encryptedDataString, Base64.NO_WRAP);
        Cipher cipher = this.cryptographyManager.getInitializedCipherForDecryption(
                context.getString(R.string.inwebo_data_secret_key),
                encryptedDataIv,
                false
        );
        if (encryptedData != null) {
            String decryptedData = this.handleDataDecryption(context, encryptedData, cipher);
            Gson gson = GsonHelper.customGson;
            return gson.fromJson(decryptedData, SecureToken.class);
        }
        return null;
    }

    @Override
    public List<SecureToken> getAll() {
        List<SecureToken> myList = new ArrayList<>();
        String[] filesList = context.fileList();
        String[] filterFilesList = Stream.of(filesList)
                .filter(str -> str.startsWith(PREFIX))
                .collect(Collectors.toSet())
                .toArray(new String[0]);

        for (String fileName : filterFilesList) {
            String filePath = new File(context.getFilesDir(), fileName).getAbsolutePath();
            String encryptedDataString = FileUtils.readFile(filePath);
            if (encryptedDataString == null) continue;

            byte[] encryptedDataIv = Base64.decode(
                    this.sharedPreferences.getString(getInitialisationVectorKeyFromJson(fileName), ""),
                    Base64.NO_WRAP
            );
            byte[] encryptedData = Base64.decode(encryptedDataString, Base64.NO_WRAP);
            Cipher cipher = this.cryptographyManager.getInitializedCipherForDecryption(
                    context.getString(R.string.inwebo_data_secret_key),
                    encryptedDataIv,
                    false
            );
            String decryptedData = this.handleDataDecryption(context, encryptedData, cipher);
            Gson gson = GsonHelper.customGson;
            SecureToken secureToken = gson.fromJson(decryptedData, SecureToken.class);
            myList.add(secureToken);
        }
        return myList;
    }

    @Override
    public boolean remove(String tknAlias) {
        SecureToken secureToken = get(tknAlias);
        if (secureToken == null) return false;
        String filePath = new File(context.getFilesDir(), getSecureTokenFileName(secureToken.getAlias())).getAbsolutePath();
        return FileUtils.removeData(filePath);
    }

    private String handleDataDecryption(Context context, byte[] encryptedData, Cipher cipher) {
        try {
            return this.cryptographyManager.decryptData(encryptedData, cipher);
        } catch (CryptographyException e) {
            SharedPreferences.Editor edit = this.sharedPreferences.edit();
            edit.remove(SHARED_PREFERENCE_DATA_KEY_IV);
            edit.apply();
            context.deleteFile(DATA_FILE_NAME);
            Log.e(TAG, "Can't decrypt stored data. Application data were flushed.", e);
        }
        return "";
    }

    private String getSecureTokenFileName(String tknAlias) {
        return (PREFIX + tknAlias + ".json");
    }

    private String getInitialisationVectorKey(String tknAlias) {
        return (PREFIX + tknAlias + ".iv.key");
    }

    private String getInitialisationVectorKeyFromJson(String jsonFile) {
        return (jsonFile.replace(".json", ".iv.key"));
    }
}
