package com.topjohnwu.magisk.utils;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.CancellationSignal;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.view.Gravity;
import android.widget.Toast;

import com.topjohnwu.core.App;
import com.topjohnwu.core.Config;
import com.topjohnwu.core.Const;
import com.topjohnwu.core.utils.Utils;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.components.CustomAlertDialog;

import java.security.KeyStore;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

@TargetApi(Build.VERSION_CODES.M)
public abstract class FingerprintHelper {

    private FingerprintManager manager;
    private Cipher cipher;
    private CancellationSignal cancel;

    public static boolean useFingerPrint() {
        boolean fp = Config.get(Config.Key.SU_FINGERPRINT);
        if (fp && !canUseFingerprint()) {
            Config.set(Config.Key.SU_FINGERPRINT, false);
            fp = false;
        }
        return fp;
    }

    public static boolean canUseFingerprint() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
            return false;
        KeyguardManager km = App.self.getSystemService(KeyguardManager.class);
        FingerprintManager fm = App.self.getSystemService(FingerprintManager.class);
        return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
    }

    public static void showAuthDialog(Activity activity, Runnable onSuccess) {
        CustomAlertDialog dialog = new CustomAlertDialog(activity);
        CustomAlertDialog.ViewHolder vh = dialog.getViewHolder();
        try {
            FingerprintHelper helper = new FingerprintHelper() {
                @Override
                public void onAuthenticationError(int errorCode, CharSequence errString) {
                    vh.messageView.setTextColor(Color.RED);
                    vh.messageView.setText(errString);
                }

                @Override
                public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
                    vh.messageView.setTextColor(Color.RED);
                    vh.messageView.setText(helpString);
                }

                @Override
                public void onAuthenticationFailed() {
                    vh.messageView.setTextColor(Color.RED);
                    vh.messageView.setText(R.string.auth_fail);
                }

                @Override
                public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
                    dialog.dismiss();
                    onSuccess.run();
                }
            };
            Drawable fingerprint = activity.getResources().getDrawable(R.drawable.ic_fingerprint);
            fingerprint.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50));
            Resources.Theme theme = activity.getTheme();
            TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint});
            fingerprint.setTint(ta.getColor(0, Color.GRAY));
            ta.recycle();
            vh.messageView.setCompoundDrawables(null, null, null, fingerprint);
            vh.messageView.setCompoundDrawablePadding(Utils.dpInPx(20));
            vh.messageView.setGravity(Gravity.CENTER);
            dialog.setMessage(R.string.auth_fingerprint)
                    .setNegativeButton(R.string.close, (d, w) -> helper.cancel())
                    .setOnCancelListener(d -> helper.cancel())
                    .show();
            helper.authenticate();
        } catch (Exception e) {
            e.printStackTrace();
            Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
        }

    }

    protected FingerprintHelper() throws Exception {
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        manager = App.self.getSystemService(FingerprintManager.class);
        cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                + KeyProperties.BLOCK_MODE_CBC + "/"
                + KeyProperties.ENCRYPTION_PADDING_PKCS7);
        keyStore.load(null);
        SecretKey key = (SecretKey) keyStore.getKey(Const.SU_KEYSTORE_KEY, null);
        if (key == null) {
            key = generateKey();
        }
        try {
            cipher.init(Cipher.ENCRYPT_MODE, key);
        } catch (KeyPermanentlyInvalidatedException e) {
            // Only happens on Marshmallow
            key = generateKey();
            cipher.init(Cipher.ENCRYPT_MODE, key);
        }
    }

    public abstract void onAuthenticationError(int errorCode, CharSequence errString);

    public abstract void onAuthenticationHelp(int helpCode, CharSequence helpString);

    public abstract void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result);

    public abstract void onAuthenticationFailed();

    public void authenticate() {
        cancel = new CancellationSignal();
        FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
        manager.authenticate(cryptoObject, cancel, 0, new Callback(), null);
    }

    public void cancel() {
        if (cancel != null)
            cancel.cancel();
    }

    private SecretKey generateKey() throws Exception {
        KeyGenerator keygen = KeyGenerator
                .getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
        KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
                Const.SU_KEYSTORE_KEY,
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                .setUserAuthenticationRequired(true)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            builder.setInvalidatedByBiometricEnrollment(false);
        }
        keygen.init(builder.build());
        return keygen.generateKey();
    }

    private class Callback extends FingerprintManager.AuthenticationCallback {
        @Override
        public void onAuthenticationError(int errorCode, CharSequence errString) {
            FingerprintHelper.this.onAuthenticationError(errorCode, errString);
        }

        @Override
        public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
            FingerprintHelper.this.onAuthenticationHelp(helpCode, helpString);
        }

        @Override
        public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
            FingerprintHelper.this.onAuthenticationSucceeded(result);
        }

        @Override
        public void onAuthenticationFailed() {
            FingerprintHelper.this.onAuthenticationFailed();
        }
    }
}
