package com.jsirnwalletcore;

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

import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;

public class CryptoManager {
  private final MasterKey masterKey;
  private final MasterKey biometricMasterKey;
  private SharedPreferences sharedPrefs;
  private SharedPreferences biometricSharedPrefs;

  public CryptoManager(Context context) throws GeneralSecurityException, IOException {
    masterKey = new MasterKey
      .Builder(context)
      .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
      .build();
    biometricMasterKey = new MasterKey
      .Builder(context)
      .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
      .setUserAuthenticationRequired(true, 300)
      .build();

    try {
      sharedPrefs = createPreferences(context, false);
      biometricSharedPrefs = createPreferences(context, true);
    } catch(Exception e) {
      Log.w(Constants.TAG, "Could not create encrypted shared preferences on initialization");
    }
  }

  private SharedPreferences createPreferences(Context context, Boolean useBiometrics) throws GeneralSecurityException, IOException {
    if(useBiometrics) {
      return EncryptedSharedPreferences.create(
        context,
        Constants.BIOMETRIC_SHARED_PREFERENCES_FILENAME,
        this.biometricMasterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
      );
    } else {
      return EncryptedSharedPreferences.create(
        context,
        Constants.SHARED_PREFERENCES_FILENAME,
        this.masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
      );
    }

  }

  private void authenticate(Context context, Activity activity, String title, String subtitle) {
    final Semaphore mutex = new Semaphore(0);
    WalletCoreAuthenticationCallback authenticationCallback = new WalletCoreAuthenticationCallback(mutex);

    final String finalTitle = title == null ? "Unlock your phone" : title;
    final String finalSubtitle = subtitle == null ? "Unlock your phone to access your wallet" : subtitle;

    activity.runOnUiThread(() -> {
      Executor executor = ContextCompat.getMainExecutor(context);
      BiometricPrompt.PromptInfo info = BiometricUtils.createBiometricPromptInfo(finalTitle, finalSubtitle);
      BiometricPrompt prompt = new BiometricPrompt((FragmentActivity) activity, executor, authenticationCallback);
      prompt.authenticate(info);
    });

    if(this.sharedPrefs == null || this.biometricSharedPrefs == null) {
      try {
        this.sharedPrefs = createPreferences(context, false);
        this.biometricSharedPrefs = createPreferences(context, true);
      } catch(Exception e) {
        Log.e(Constants.TAG, "Could not create shared preferences: " + e.getLocalizedMessage());
      }
    }

    try {
      mutex.acquire();
    } catch (InterruptedException e) {
      Log.e(Constants.TAG, "Interrupted mutex exception");
    }

    Log.w(Constants.TAG, "User authenticated!");
  }

  public void set(Context context, Activity activity, boolean useBiometrics, String key, String value, String title, String subtitle) {
    if(useBiometrics) {
      authenticate(context, activity, title, subtitle);
      biometricSharedPrefs.edit().putString(key, value).apply();
    } else {
      sharedPrefs.edit().putString(key, value).apply();
    }
  }


  public String get(Context context, Activity activity, boolean useBiometrics, String key, String title, String subtitle) {
    if(useBiometrics) {
      authenticate(context, activity, title, subtitle);
      return biometricSharedPrefs.getString(key, null);
    } else {
      return sharedPrefs.getString(key, null);
    }
  }

}
