package org.thoughtcrime.securesms

import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContract
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.PromptInfo
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.util.ServiceUtil

/**
 * Authentication using phone biometric (face, fingerprint recognition) or device lock (pattern, pin or passphrase).
 */
class BiometricDeviceAuthentication(
  private val biometricManager: BiometricManager,
  private val biometricPrompt: BiometricPrompt,
  private var biometricPromptInfo: PromptInfo
) {
  companion object {
    const val AUTHENTICATED = 1
    const val NOT_AUTHENTICATED = -1
    const val TAG: String = "BiometricDeviceAuth"
    const val BIOMETRIC_AUTHENTICATORS = BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.BIOMETRIC_WEAK
    const val ALLOWED_AUTHENTICATORS = BIOMETRIC_AUTHENTICATORS or BiometricManager.Authenticators.DEVICE_CREDENTIAL

    /**
     * From the docs on [BiometricManager.canAuthenticate]
     *
     * > Note that not all combinations of authenticator types are supported prior to Android 11 (API 30). Specifically, DEVICE_CREDENTIAL alone is unsupported
     * > prior to API 30, and BIOMETRIC_STRONG | DEVICE_CREDENTIAL is unsupported on API 28-29.
     */
    private val DISALLOWED_BIOMETRIC_VERSIONS = setOf(28, 29)
  }

  private fun isDeviceSecure(context: Context): Boolean {
    return ServiceUtil.getKeyguardManager(context).isDeviceSecure
  }

  /**
   * From the docs on [BiometricManager.canAuthenticate]
   *
   * > Note that not all combinations of authenticator types are supported prior to Android 11 (API 30).
   * > Developers that wish to check for the presence of a PIN, pattern, or password on these versions should instead use KeyguardManager.isDeviceSecure().
   */
  fun canAuthenticate(context: Context): Boolean {
    return if (Build.VERSION.SDK_INT >= 30) {
      biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS
    } else {
      biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS || isDeviceSecure(context)
    }
  }

  /**
   * Returns whether the device credentials education sheet should be shown (only when biometrics is not enabled)
   */
  fun shouldShowEducationSheet(context: Context): Boolean {
    return canAuthenticate(context) && biometricManager.canAuthenticate(BIOMETRIC_AUTHENTICATORS) != BiometricManager.BIOMETRIC_SUCCESS
  }

  private fun isDontKeepActivitiesOn(context: Context): Boolean {
    return Build.VERSION.SDK_INT < 30 && Settings.Global.getInt(context.contentResolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0
  }

  fun authenticate(context: Context, force: Boolean, showConfirmDeviceCredentialIntent: () -> Unit): Boolean {
    if (!canAuthenticate(context)) {
      Log.w(TAG, "Cannot authenticate, skipping. isDeviceSecure: ${isDeviceSecure(context)}, Auth status: ${biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS)}")
      return false
    }

    return if (
      !isDontKeepActivitiesOn(context) &&
      !DISALLOWED_BIOMETRIC_VERSIONS.contains(Build.VERSION.SDK_INT) &&
      biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS
    ) {
      if (force) {
        Log.i(TAG, "Listening for biometric authentication...")
        try {
          biometricPrompt.authenticate(biometricPromptInfo)
        } catch (e: ActivityNotFoundException) {
          Log.w(TAG, "Failed to launch confirm device credential settings", e)
          return false
        }
      } else {
        Log.i(TAG, "Skipping show system biometric or device lock dialog unless forced")
      }
      true
    } else {
      if (force) {
        Log.i(TAG, "firing intent...")
        showConfirmDeviceCredentialIntent()
      } else {
        Log.i(TAG, "Skipping firing intent unless forced")
      }
      true
    }
  }

  fun cancelAuthentication() {
    biometricPrompt.cancelAuthentication()
  }

  fun updatePromptInfo(promptInfo: PromptInfo) {
    biometricPromptInfo = promptInfo
  }
}

class BiometricDeviceLockContract : ActivityResultContract<String, Int>() {

  override fun createIntent(context: Context, input: String): Intent {
    val keyguardManager = ServiceUtil.getKeyguardManager(context)
    return keyguardManager.createConfirmDeviceCredentialIntent(input, "")
  }

  override fun parseResult(resultCode: Int, intent: Intent?) = if (resultCode != Activity.RESULT_OK) {
    BiometricDeviceAuthentication.NOT_AUTHENTICATED
  } else {
    BiometricDeviceAuthentication.AUTHENTICATED
  }
}
