/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.settings.biometric

import android.content.Context
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.M
import androidx.annotation.VisibleForTesting
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.settings.biometric.ext.isEnrolled
import org.mozilla.fenix.settings.biometric.ext.isHardwareAvailable

/**
 * A [LifecycleAwareFeature] for the Android Biometric API to prompt for user authentication.
 *
 * @param context Android context.
 * @param fragment The fragment on which this feature will live.
 * @param onAuthSuccess A success callback.
 * @param onAuthFailure A failure callback if authentication failed.
 */
class BiometricPromptFeature(
    private val context: Context,
    private val fragment: Fragment,
    private val onAuthFailure: () -> Unit,
    private val onAuthSuccess: () -> Unit,
) : LifecycleAwareFeature {
    private val logger = Logger(javaClass.simpleName)

    @VisibleForTesting
    internal var biometricPrompt: BiometricPrompt? = null

    override fun start() {
        val executor = ContextCompat.getMainExecutor(context)
        biometricPrompt = BiometricPrompt(fragment, executor, PromptCallback())
    }

    override fun stop() {
        biometricPrompt = null
    }

    /**
     * Requests the user for biometric authentication.
     *
     * @param title Adds a title for the authentication prompt.
     */
    fun requestAuthentication(title: String) {
        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setAllowedAuthenticators(BIOMETRIC_WEAK or DEVICE_CREDENTIAL)
            .setTitle(title)
            .build()

        biometricPrompt?.authenticate(promptInfo)
    }

    internal inner class PromptCallback : BiometricPrompt.AuthenticationCallback() {
        override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
            logger.error("onAuthenticationError $errString")
            onAuthFailure.invoke()
        }

        override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
            logger.debug("onAuthenticationSucceeded")
            onAuthSuccess.invoke()
        }

        override fun onAuthenticationFailed() {
            logger.error("onAuthenticationFailed")
            onAuthFailure.invoke()
        }
    }

    companion object {

        /**
         * Checks if the appropriate SDK version and hardware capabilities are met to use the feature.
         */
        fun canUseFeature(context: Context): Boolean {
            return if (SDK_INT >= M) {
                val manager = BiometricManager.from(context)

                manager.isHardwareAvailable() && manager.isEnrolled()
            } else {
                false
            }
        }
    }
}
