package de.niklasmerz.cordova.biometric;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;


import androidx.biometric.BiometricManager;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class Fingerprint extends CordovaPlugin {

    private static final String TAG = "Fingerprint";
    private static final int REQUEST_CODE_BIOMETRIC = 1;

    private CallbackContext mCallbackContext = null;
    private PromptInfo.Builder mPromptInfoBuilder;

    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
        super.initialize(cordova, webView);
        Log.v(TAG, "Init Fingerprint");
        mPromptInfoBuilder = new PromptInfo.Builder(
            this.getApplicationLabel(cordova.getActivity())
        );
    }

    public boolean execute(final String action, JSONArray args, CallbackContext callbackContext) {

        this.mCallbackContext = callbackContext;
        Log.v(TAG, "Fingerprint action: " + action);

        if ("authenticate".equals(action)) {
            executeAuthenticate(args);
            return true;

        } else if ("registerBiometricSecret".equals(action)) {
             executeRegisterBiometricSecret(args);
             return true;

         } else if ("loadBiometricSecret".equals(action)) {
             executeLoadBiometricSecret(args);
             return true;

         } else if ("isAvailable".equals(action)) {
            executeIsAvailable(args);
            return true;

        }
        return false;
    }

    private void executeIsAvailable(JSONArray args) {
        boolean requireStrongBiometrics = new Args(args).getBoolean("requireStrongBiometrics", false);
        PluginError error = canAuthenticate(requireStrongBiometrics);
        if (error != null) {
            sendError(error);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            sendSuccess("biometric");
        } else {
            sendSuccess("finger");
        }
    }
    private void executeRegisterBiometricSecret(JSONArray args) {
        // should at least contains the secret
        if (args == null) {
            sendError(PluginError.BIOMETRIC_ARGS_PARSING_FAILED);
            return;
        }
        this.runBiometricActivity(args, BiometricActivityType.REGISTER_SECRET);
    }

    private void executeLoadBiometricSecret(JSONArray args) {
        this.runBiometricActivity(args, BiometricActivityType.LOAD_SECRET);
    }

    private void executeAuthenticate(JSONArray args) {
        this.runBiometricActivity(args, BiometricActivityType.JUST_AUTHENTICATE);
    }

    private boolean determineStrongBiometricsRequired(BiometricActivityType type) {
        return type == BiometricActivityType.REGISTER_SECRET || type == BiometricActivityType.LOAD_SECRET;
    }

    private void runBiometricActivity(JSONArray args, BiometricActivityType type) {
        boolean requireStrongBiometrics = determineStrongBiometricsRequired(type);
        PluginError error = canAuthenticate(requireStrongBiometrics);
        if (error != null) {
            sendError(error);
            return;
        }
        cordova.getActivity().runOnUiThread(() -> {
            mPromptInfoBuilder.parseArgs(args, type);
            Intent intent = new Intent(cordova.getActivity().getApplicationContext(), BiometricActivity.class);
            intent.putExtras(mPromptInfoBuilder.build().getBundle());
            this.cordova.startActivityForResult(this, intent, REQUEST_CODE_BIOMETRIC);
        });
        PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
        pluginResult.setKeepCallback(true);
        this.mCallbackContext.sendPluginResult(pluginResult);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (requestCode != REQUEST_CODE_BIOMETRIC) {
            return;
        }
        if (resultCode != Activity.RESULT_OK) {
            sendError(intent);
            return;
        }
        sendSuccess(intent);
    }

    private void sendSuccess(Intent intent) {
        if (intent != null && intent.getExtras() != null) {
            sendSuccess(intent.getExtras().getString(PromptInfo.SECRET_EXTRA));
        } else {
            sendSuccess("biometric_success");
        }
    }

    private void sendError(Intent intent) {
        if (intent != null) {
            Bundle extras = intent.getExtras();
            sendError(extras.getInt("code"), extras.getString("message"));
        } else {
            sendError(PluginError.BIOMETRIC_DISMISSED);
        }
    }

    private PluginError canAuthenticate(boolean requireStrongBiometrics) {
        int error = BiometricManager.from(cordova.getContext()).canAuthenticate(requireStrongBiometrics ? BiometricManager.Authenticators.BIOMETRIC_STRONG : BiometricManager.Authenticators.BIOMETRIC_WEAK);
        switch (error) {
            case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
            case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
                return PluginError.BIOMETRIC_HARDWARE_NOT_SUPPORTED;
            case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
                return PluginError.BIOMETRIC_NOT_ENROLLED;
            case BiometricManager.BIOMETRIC_SUCCESS:
            default:
                return null;
        }
    }

    private void sendError(int code, String message) {
        JSONObject resultJson = new JSONObject();
        try {
            resultJson.put("code", code);
            resultJson.put("message", message);

            PluginResult result = new PluginResult(PluginResult.Status.ERROR, resultJson);
            result.setKeepCallback(true);
            if (cordova.getActivity() != null) {
                if(Fingerprint.this.mCallbackContext != null){
                    cordova.getActivity().runOnUiThread(() ->
                            Fingerprint.this.mCallbackContext.sendPluginResult(result));
                }
                else{
                    Log.e(TAG, code + ":" + message);
                }
            } else {
                Log.e(TAG, "Cordova activity does not exist.");
            }
        } catch (JSONException e) {
            Log.e(TAG, e.getMessage(), e);
        }
    }

    private void sendError(PluginError error) {
        sendError(error.getValue(), error.getMessage());
    }

    private void sendSuccess(String message) {
        cordova.getActivity().runOnUiThread(() ->
                this.mCallbackContext.success(message));
    }

    private String getApplicationLabel(Context context) {
        try {
            PackageManager packageManager = context.getPackageManager();
            ApplicationInfo app = packageManager
                    .getApplicationInfo(context.getPackageName(), 0);
            return packageManager.getApplicationLabel(app).toString();
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
    }
}
