/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.biometrics.fingerprint2.ui.enrollment.activity

import android.app.Activity
import android.content.Intent
import android.content.res.Configuration
import android.hardware.fingerprint.FingerprintEnrollOptions
import android.hardware.fingerprint.FingerprintManager
import android.os.Bundle
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.android.settings.R
import com.android.settings.SetupWizardUtils
import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
import com.android.settings.biometrics.BiometricUtils
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.Default
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.RfpsEnrollFindSensorFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.SfpsEnrollFindSensorFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.UdfpsEnrollFindSensorFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.fragment.UdfpsEnrollFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsLastStepViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintFlowViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.ConfirmDeviceCredential
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Confirmation
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Enrollment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Init
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Introduction
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.TransitionStep
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Transition
import com.android.settings.flags.Flags
import com.android.settings.password.ChooseLockGeneric
import com.android.settings.password.ChooseLockSettingsHelper
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
import com.android.settingslib.display.DisplayDensityUtils
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.google.android.setupcompat.util.WizardManagerHelper
import com.google.android.setupdesign.util.ThemeHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch

private const val TAG = "FingerprintEnrollmentV2Activity"

/**
 * This is the activity that controls the entire Fingerprint Enrollment experience through its
 * children fragments.
 */
class FingerprintEnrollmentV2Activity : FragmentActivity() {
  private val navigationViewModel: FingerprintNavigationViewModel by viewModels {
    FingerprintNavigationViewModel.Factory
  }
  private val fingerprintFlowViewModel: FingerprintFlowViewModel by viewModels {
    FingerprintFlowViewModel.Factory
  }

  private val gatekeeperViewModel: FingerprintGatekeeperViewModel by viewModels {
    FingerprintGatekeeperViewModel.Factory
  }

  /**
   * View models below this line are not used by this class but must be initialized in the activity
   * view model store to be used by other view models.
   */
  private val fingerprintEnrollViewModel: FingerprintEnrollViewModel by viewModels {
    FingerprintEnrollViewModel.Factory
  }

  private val fingerprintEnrollEnrollingViewModel:
    FingerprintEnrollEnrollingViewModel by viewModels {
    FingerprintEnrollEnrollingViewModel.Factory
  }

  private val udfpsLastStepViewModel: UdfpsLastStepViewModel by viewModels {
    UdfpsLastStepViewModel.Factory
  }

  private val backgroundViewModel: BackgroundViewModel by viewModels { BackgroundViewModel.Factory }

  private lateinit var foldStateInteractor: FoldStateInteractor
  private lateinit var displayDensityInteractor: DisplayDensityInteractor
  private val coroutineDispatcher = Dispatchers.Default

  /** Result listener for ChooseLock activity flow. */
  private val confirmDeviceResultListener =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
      val resultCode = result.resultCode
      val data = result.data
      onConfirmDevice(resultCode, data)
    }

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == CONFIRM_REQUEST) {
      onConfirmDevice(resultCode, data)
    }
  }

  override fun onStop() {
    super.onStop()
    if (!isChangingConfigurations) {
      backgroundViewModel.wentToBackground()
    }
  }

  override fun onResume() {
    super.onResume()
    backgroundViewModel.inForeground()
  }

  override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    foldStateInteractor.onConfigurationChange(newConfig)
    val displayDensityUtils = DisplayDensityUtils(applicationContext)
    val currIndex = displayDensityUtils.currentIndex
    displayDensityInteractor.updateFontScale(resources.configuration.fontScale)
    displayDensityUtils.values?.let {
      displayDensityInteractor.updateDisplayDensity(it[currIndex])
    }
  }

  private fun onConfirmDevice(resultCode: Int, data: Intent?) {
    val wasSuccessful = resultCode == RESULT_FINISHED || resultCode == Activity.RESULT_OK
    val gateKeeperPasswordHandle = data?.getExtra(EXTRA_KEY_GK_PW_HANDLE) as Long

    lifecycleScope.launch {
      val confirmDeviceResult =
        if (wasSuccessful) {
          FingerprintAction.CONFIRM_DEVICE_SUCCESS
        } else {
          FingerprintAction.CONFIRM_DEVICE_FAIL
        }
      gatekeeperViewModel.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
      navigationViewModel.update(
        confirmDeviceResult,
        ConfirmDeviceCredential::class,
        "$TAG#onConfirmDevice",
      )
    }
  }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // TODO(b/299573056): Show split screen dialog when it's in multi window mode.
    setContentView(R.layout.fingerprint_v2_enroll_main)

    if (!Flags.fingerprintV2Enrollment()) {
      check(false) {
        "fingerprint enrollment v2 is not enabled, " +
          "please run adb shell device_config put " +
          "biometrics_framework com.android.settings.flags.fingerprint_v2_enrollment true"
      }
      finish()
    }

    // Ensure that these view models are actually created and in this order
    navigationViewModel
    fingerprintFlowViewModel
    gatekeeperViewModel
    fingerprintEnrollViewModel
    backgroundViewModel
    fingerprintEnrollEnrollingViewModel
    udfpsLastStepViewModel

    setTheme(SetupWizardUtils.getTheme(applicationContext, intent))
    ThemeHelper.trySetDynamicColor(applicationContext)

    val backgroundDispatcher = Dispatchers.IO

    val context = applicationContext
    val fingerprintManager = context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager
    val isAnySuw = WizardManagerHelper.isAnySetupWizard(intent)
    val enrollType =
      if (isAnySuw) {
        SetupWizard
      } else {
        Default
      }

    fingerprintFlowViewModel.updateFlowType(enrollType)

    if (intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) === -1) {
      val isSuw: Boolean = WizardManagerHelper.isAnySetupWizard(intent)
      intent.putExtra(
        BiometricUtils.EXTRA_ENROLL_REASON,
        if (isSuw) FingerprintEnrollOptions.ENROLL_REASON_SUW
        else FingerprintEnrollOptions.ENROLL_REASON_SETTINGS,
      )
    }

    var challenge = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
    val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
    val gatekeeperInfo = FingerprintGatekeeperViewModel.toGateKeeperInfo(challenge, token)

    val hasConfirmedDeviceCredential = gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo

    navigationViewModel.updateFingerprintFlow(enrollType)
    navigationViewModel.hasConfirmedDeviceCredential(hasConfirmedDeviceCredential)

    lifecycleScope.launch {
      navigationViewModel.currentStep.collect { step ->
        if (step is Init) {
          navigationViewModel.update(FingerprintAction.ACTIVITY_CREATED, Init::class, "$TAG#init")
        }
      }
    }

    lifecycleScope.launch {
      navigationViewModel.navigateTo.filterNotNull().collect { step ->
        Log.d(TAG, "navigateTo: $step")
        if (step is ConfirmDeviceCredential) {
          launchConfirmOrChooseLock(userId)
          navigationViewModel.update(
            FingerprintAction.TRANSITION_FINISHED,
            TransitionStep::class,
            "$TAG#launchConfirmOrChooseLock",
          )
        } else {
          val theClass: Fragment? =
            when (step) {
              Confirmation -> FingerprintEnrollConfirmationV2Fragment()
              is Education -> {
                when (step.sensor.sensorType) {
                  FingerprintSensorType.REAR -> RfpsEnrollFindSensorFragment()
                  FingerprintSensorType.UDFPS_OPTICAL,
                  FingerprintSensorType.UDFPS_ULTRASONIC -> UdfpsEnrollFindSensorFragment()
                  else -> SfpsEnrollFindSensorFragment()
                }
              }
              is Enrollment -> {
                when (step.sensor.sensorType) {
                  FingerprintSensorType.REAR -> RFPSEnrollFragment()
                  FingerprintSensorType.UDFPS_OPTICAL,
                  FingerprintSensorType.UDFPS_ULTRASONIC -> UdfpsEnrollFragment()
                  else -> FingerprintEnrollEnrollingV2Fragment()
                }
              }
              is Introduction -> FingerprintEnrollIntroV2Fragment()
              else -> null
            }

          if (theClass != null) {
            supportFragmentManager
              .beginTransaction()
              .setCustomAnimations(
                step.enterTransition.toAnimation(),
                step.exitTransition.toAnimation(),
              )
              .setReorderingAllowed(true)
              .replace(R.id.fragment_container_view, theClass::class.java, null)
              .commit()
            navigationViewModel.update(
              FingerprintAction.TRANSITION_FINISHED,
              TransitionStep::class,
              "$TAG#fragmentManager.add($theClass)",
            )
          }
        }
      }
    }

    lifecycleScope.launch {
      navigationViewModel.shouldFinish.filterNotNull().collect {
        Log.d(TAG, "FingerprintSettingsNav.finishing($it)")
        if (it.result != null) {
          finishActivity(it.result)
        } else {
          finish()
        }
      }
    }

    lifecycleScope.launch {
      navigationViewModel.currentScreen.filterNotNull().collect { screen ->
        Log.d(TAG, "FingerprintSettingsNav.currentScreen($screen)")
      }
    }

    val fromSettingsSummary =
      intent.getBooleanExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, false)
    if (
      fromSettingsSummary && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)
    ) {
      overridePendingTransition(
        com.google.android.setupdesign.R.anim.sud_slide_next_in,
        com.google.android.setupdesign.R.anim.sud_slide_next_out,
      )
    }
  }

  private fun launchConfirmOrChooseLock(userId: Int) {
    val activity = this
    lifecycleScope.launch(coroutineDispatcher) {
      val intent = Intent()
      val builder = ChooseLockSettingsHelper.Builder(activity)
      val launched =
        builder
          .setRequestCode(CONFIRM_REQUEST)
          .setTitle(getString(R.string.security_settings_fingerprint_preference_title))
          .setRequestGatekeeperPasswordHandle(true)
          .setUserId(userId)
          .setForegroundOnly(true)
          .setReturnCredentials(true)
          .show()
      if (!launched) {
        intent.setClassName(SETTINGS_PACKAGE_NAME, ChooseLockGeneric::class.java.name)
        intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true)
        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true)
        intent.putExtra(Intent.EXTRA_USER_ID, userId)
        confirmDeviceResultListener.launch(intent)
      }
    }
  }
}

private fun Transition.toAnimation(): Int {
  return when (this) {
    Transition.EnterFromLeft -> com.google.android.setupdesign.R.anim.sud_slide_back_in
    Transition.EnterFromRight -> com.google.android.setupdesign.R.anim.sud_slide_next_in
    Transition.ExitToLeft -> com.google.android.setupdesign.R.anim.sud_slide_next_out
    Transition.ExitToRight -> com.google.android.setupdesign.R.anim.sud_slide_back_out
  }
}
