/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

import android.app.AlertDialog
import android.app.Dialog
import android.content.DialogInterface
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import android.text.Html
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.DialogFragment
import com.facebook.AccessToken
import com.facebook.AccessTokenSource
import com.facebook.FacebookActivity
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.facebook.GraphRequest
import com.facebook.GraphRequestAsyncTask
import com.facebook.HttpMethod
import com.facebook.appevents.InternalAppEventsLogger
import com.facebook.common.R
import com.facebook.devicerequests.internal.DeviceRequestsHelper
import com.facebook.internal.AnalyticsEvents
import com.facebook.internal.FetchedAppSettingsManager
import com.facebook.internal.SmartLoginOption
import com.facebook.internal.Utility
import com.facebook.internal.Validate
import com.facebook.login.LoginClient.Request
import java.util.Date
import java.util.Locale
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.jvm.Volatile
import org.json.JSONException
import org.json.JSONObject

open class DeviceAuthDialog : DialogFragment() {
  private lateinit var progressBar: View
  private lateinit var confirmationCode: TextView
  private lateinit var instructions: TextView
  private var deviceAuthMethodHandler: DeviceAuthMethodHandler? = null
  private val completed = AtomicBoolean()

  @Volatile private var currentGraphRequestPoll: GraphRequestAsyncTask? = null
  @Volatile private var scheduledPoll: ScheduledFuture<*>? = null
  @Volatile private var currentRequestState: RequestState? = null

  // Used to tell if we are destroying the fragment because it was dismissed or dismissing the
  // fragment because it is being destroyed.
  private var isBeingDestroyed = false
  private var isRetry = false
  private var request: Request? = null

  override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup?,
      savedInstanceState: Bundle?
  ): View? {
    val view = super.onCreateView(inflater, container, savedInstanceState)
    val facebookActivity = requireActivity() as FacebookActivity
    val fragment = facebookActivity.currentFragment as LoginFragment?
    deviceAuthMethodHandler = fragment?.loginClient?.getCurrentHandler() as DeviceAuthMethodHandler?
    savedInstanceState?.getParcelable<RequestState>(REQUEST_STATE_KEY)?.let { requestState ->
      setCurrentRequestState(requestState)
    }
    return view
  }

  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val dialog: Dialog =
        object : Dialog(requireActivity(), R.style.com_facebook_auth_dialog) {
          override fun onBackPressed() {
            if (onBackButtonPressed()) {
              super.onBackPressed()
            }
          }
        }
    dialog.setContentView(initializeContentView(DeviceRequestsHelper.isAvailable() && !isRetry))
    return dialog
  }

  override fun onDismiss(dialog: DialogInterface) {
    super.onDismiss(dialog)
    if (!isBeingDestroyed) {
      onCancel()
    }
  }

  override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    if (currentRequestState != null) {
      outState.putParcelable(REQUEST_STATE_KEY, currentRequestState)
    }
  }

  override fun onDestroyView() {
    // Set this to true so we know if we are being destroyed and then dismissing the dialog
    // Or if we are dismissing the dialog and then destroying the fragment. In latter we want
    // to do a cancel callback.
    isBeingDestroyed = true
    completed.set(true)
    super.onDestroyView()
    currentGraphRequestPoll?.cancel(true)
    scheduledPoll?.cancel(true)
  }

  /**
   * Start the login/auth process with a login request
   * @param request the login request
   */
  open fun startLogin(request: Request) {
    this.request = request
    val parameters = Bundle()
    parameters.putString("scope", TextUtils.join(",", request.permissions))
    Utility.putNonEmptyString(parameters, "redirect_uri", request.deviceRedirectUriString)
    Utility.putNonEmptyString(
        parameters, DeviceRequestsHelper.DEVICE_TARGET_USER_ID, request.deviceAuthTargetUserId)
    val appAccessToken = getApplicationAccessToken()
    parameters.putString(GraphRequest.ACCESS_TOKEN_PARAM, appAccessToken)
    parameters.putString(
        DeviceRequestsHelper.DEVICE_INFO_PARAM,
        DeviceRequestsHelper.getDeviceInfo(additionalDeviceInfo()?.toMutableMap()))
    val graphRequest =
        GraphRequest.newPostRequestWithBundle(
            null,
            DEVICE_LOGIN_ENDPOINT,
            parameters,
            GraphRequest.Callback { response ->
              if (isBeingDestroyed) {
                return@Callback
              }
              if (response.error != null) {
                onError(response.error?.exception ?: FacebookException())
                return@Callback
              }
              val jsonObject = response.getJSONObject() ?: JSONObject()
              val requestState = RequestState()
              try {
                requestState.setUserCode(jsonObject.getString("user_code"))
                requestState.requestCode = jsonObject.getString("code")
                requestState.interval = jsonObject.getLong("interval")
              } catch (ex: JSONException) {
                onError(FacebookException(ex))
                return@Callback
              }
              setCurrentRequestState(requestState)
            })
    graphRequest.executeAsync()
  }

  /** Additional device information for this device auth. It's only for internal use. */
  open fun additionalDeviceInfo(): Map<String, String>? = null

  open fun getApplicationAccessToken(): String =
      "${Validate.hasAppID()}|${Validate.hasClientToken()}"

  private fun setCurrentRequestState(currentRequestState: RequestState) {
    this.currentRequestState = currentRequestState
    confirmationCode.text = currentRequestState.getUserCode()
    val bitmap = DeviceRequestsHelper.generateQRCode(currentRequestState.authorizationUri)
    val qrCode = BitmapDrawable(resources, bitmap)
    instructions.setCompoundDrawablesWithIntrinsicBounds(null, qrCode, null, null)
    confirmationCode.visibility = View.VISIBLE
    progressBar.visibility = View.GONE
    if (!isRetry) {
      if (DeviceRequestsHelper.startAdvertisementService(currentRequestState.getUserCode())) {
        val logger = InternalAppEventsLogger(context)
        logger.logEventImplicitly(AnalyticsEvents.EVENT_SMART_LOGIN_SERVICE)
      }
    }

    // If we polled within the last interval schedule a poll else start a poll.
    if (currentRequestState.withinLastRefreshWindow()) {
      schedulePoll()
    } else {
      poll()
    }
  }

  protected open fun initializeContentView(isSmartLogin: Boolean): View {
    val view: View
    val inflater = requireActivity().layoutInflater
    view = inflater.inflate(getLayoutResId(isSmartLogin), null)
    progressBar = view.findViewById(R.id.progress_bar)
    confirmationCode = view.findViewById<View>(R.id.confirmation_code) as TextView
    val cancelButton = view.findViewById<View>(R.id.cancel_button) as Button
    cancelButton.setOnClickListener { onCancel() }
    instructions = view.findViewById<View>(R.id.com_facebook_device_auth_instructions) as TextView
    instructions.text = Html.fromHtml(getString(R.string.com_facebook_device_auth_instructions))
    return view
  }

  @LayoutRes
  protected open fun getLayoutResId(isSmartLogin: Boolean): Int {
    return if (isSmartLogin) R.layout.com_facebook_smart_device_dialog_fragment
    else R.layout.com_facebook_device_auth_dialog_fragment
  }

  private fun poll() {
    currentRequestState?.setLastPoll(Date().time)
    currentGraphRequestPoll = pollRequest.executeAsync()
  }

  private fun schedulePoll() {
    val interval = currentRequestState?.interval
    if (interval != null) {
      scheduledPoll =
          DeviceAuthMethodHandler.getBackgroundExecutor()
              .schedule({ poll() }, interval, TimeUnit.SECONDS)
    }
  }

  private val pollRequest: GraphRequest
    get() {
      val parameters = Bundle()
      parameters.putString("code", currentRequestState?.requestCode)
      val appAccessToken = getApplicationAccessToken()
      parameters.putString(GraphRequest.ACCESS_TOKEN_PARAM, appAccessToken)
      return GraphRequest.newPostRequestWithBundle(
          null,
          DEVICE_LOGIN_STATUS_ENDPOINT,
          parameters,
          GraphRequest.Callback { response -> // Check if the request was already cancelled
            if (completed.get()) {
              return@Callback
            }
            val error = response.error
            if (error != null) {
              // We need to decide if this is a fatal error by checking the error
              // message text
              when (error.subErrorCode) {
                LOGIN_ERROR_SUBCODE_AUTHORIZATION_PENDING,
                LOGIN_ERROR_SUBCODE_EXCESSIVE_POLLING -> {

                  // Keep polling. If we got the slow down message just ignore
                  schedulePoll()
                }
                LOGIN_ERROR_SUBCODE_CODE_EXPIRED -> {
                  currentRequestState?.let {
                    DeviceRequestsHelper.cleanUpAdvertisementService(it.getUserCode())
                  }
                  val request = this.request
                  if (request != null) {
                    startLogin(request)
                  } else {
                    onCancel()
                  }
                }
                LOGIN_ERROR_SUBCODE_AUTHORIZATION_DECLINED -> {
                  onCancel()
                }
                else -> {
                  onError(response.error?.exception ?: FacebookException())
                }
              }
              return@Callback
            }
            try {
              val resultObject = response.getJSONObject() ?: JSONObject()
              onSuccess(
                  resultObject.getString("access_token"),
                  resultObject.getLong("expires_in"),
                  resultObject.optLong("data_access_expiration_time"))
            } catch (ex: JSONException) {
              onError(FacebookException(ex))
            }
          })
    }

  private fun presentConfirmation(
      userId: String,
      permissions: PermissionsLists,
      accessToken: String,
      name: String,
      expirationTime: Date?,
      dataAccessExpirationTime: Date?
  ) {
    val message = resources.getString(R.string.com_facebook_smart_login_confirmation_title)
    val continueFormat =
        resources.getString(R.string.com_facebook_smart_login_confirmation_continue_as)
    val cancel = resources.getString(R.string.com_facebook_smart_login_confirmation_cancel)
    val continueText = String.format(continueFormat, name)
    val builder = AlertDialog.Builder(context)
    builder
        .setMessage(message)
        .setCancelable(true)
        .setNegativeButton(continueText) { _, _ ->
          completeLogin(userId, permissions, accessToken, expirationTime, dataAccessExpirationTime)
        }
        .setPositiveButton(cancel) { _, _ ->
          val view = initializeContentView(false)
          dialog?.setContentView(view)
          request?.let { startLogin(it) }
        }
    builder.create().show()
  }

  private fun onSuccess(accessToken: String, expiresIn: Long, dataAccessExpirationTime: Long?) {
    val parameters = Bundle()
    parameters.putString(GraphRequest.FIELDS_PARAM, "id,permissions,name")
    val expirationTime = if (expiresIn != 0L) Date(Date().time + expiresIn * 1000L) else null
    val dataAccessExpirationTimeDate =
        if (dataAccessExpirationTime != 0L && dataAccessExpirationTime != null)
            Date(dataAccessExpirationTime * 1000L)
        else null
    val temporaryToken =
        AccessToken(
            accessToken,
            FacebookSdk.getApplicationId(),
            "0",
            null,
            null,
            null,
            null,
            expirationTime,
            null,
            dataAccessExpirationTimeDate)
    val request =
        GraphRequest.newGraphPathRequest(
            temporaryToken,
            "me",
            GraphRequest.Callback { response ->
              if (completed.get()) {
                return@Callback
              }
              val error = response.error
              if (error != null) {
                onError(error.exception ?: FacebookException())
                return@Callback
              }
              val userId: String
              val permissions: PermissionsLists
              val name: String
              try {
                val jsonObject = response.getJSONObject() ?: JSONObject()
                userId = jsonObject.getString("id")
                permissions = handlePermissionResponse(jsonObject)
                name = jsonObject.getString("name")
              } catch (ex: JSONException) {
                onError(FacebookException(ex))
                return@Callback
              }
              currentRequestState?.let {
                DeviceRequestsHelper.cleanUpAdvertisementService(it.getUserCode())
              }
              val requireConfirm =
                  FetchedAppSettingsManager.getAppSettingsWithoutQuery(
                          FacebookSdk.getApplicationId())
                      ?.smartLoginOptions
                      ?.contains(SmartLoginOption.RequireConfirm)
              if (requireConfirm == true && !isRetry) {
                isRetry = true
                presentConfirmation(
                    userId,
                    permissions,
                    accessToken,
                    name,
                    expirationTime,
                    dataAccessExpirationTimeDate)
                return@Callback
              }
              completeLogin(
                  userId, permissions, accessToken, expirationTime, dataAccessExpirationTimeDate)
            })
    request.httpMethod = HttpMethod.GET
    request.parameters = parameters
    request.executeAsync()
  }

  private fun completeLogin(
      userId: String,
      permissions: PermissionsLists,
      accessToken: String,
      expirationTime: Date?,
      dataAccessExpirationTime: Date?
  ) {
    deviceAuthMethodHandler?.onSuccess(
        accessToken,
        FacebookSdk.getApplicationId(),
        userId,
        permissions.grantedPermissions,
        permissions.declinedPermissions,
        permissions.expiredPermissions,
        AccessTokenSource.DEVICE_AUTH,
        expirationTime,
        null,
        dataAccessExpirationTime)
    dialog?.dismiss()
  }

  protected open fun onError(ex: FacebookException) {
    if (!completed.compareAndSet(false, true)) {
      return
    }
    currentRequestState?.let { DeviceRequestsHelper.cleanUpAdvertisementService(it.getUserCode()) }
    deviceAuthMethodHandler?.onError(ex)
    dialog?.dismiss()
  }

  protected open fun onCancel() {
    if (!completed.compareAndSet(false, true)) {
      // Should not have happened but we called cancel twice
      return
    }
    currentRequestState?.let { DeviceRequestsHelper.cleanUpAdvertisementService(it.getUserCode()) }
    // We are detached and cannot send a cancel message back
    deviceAuthMethodHandler?.onCancel()
    dialog?.dismiss()
  }

  protected open fun onBackButtonPressed(): Boolean {
    return true
  }

  private class RequestState : Parcelable {
    var authorizationUri: String? = null
      private set
    private var userCode: String? = null
    var requestCode: String? = null
    var interval: Long = 0
    private var lastPoll: Long = 0
    internal constructor()
    fun getUserCode(): String? = userCode

    fun setUserCode(userCode: String?) {
      this.userCode = userCode
      authorizationUri =
          String.format(
              Locale.ENGLISH, "https://facebook.com/device?user_code=%1\$s&qr=1", userCode)
    }

    fun setLastPoll(lastPoll: Long) {
      this.lastPoll = lastPoll
    }
    protected constructor(parcel: Parcel) {
      authorizationUri = parcel.readString()
      userCode = parcel.readString()
      requestCode = parcel.readString()
      interval = parcel.readLong()
      lastPoll = parcel.readLong()
    }

    /** @return True if the current time is less than last poll time + polling interval. */
    fun withinLastRefreshWindow(): Boolean {
      if (lastPoll == 0L) {
        return false
      }
      val diff = Date().time - lastPoll - interval * 1000L
      return diff < 0
    }

    override fun describeContents(): Int = 0

    override fun writeToParcel(dest: Parcel, flags: Int) {
      dest.writeString(authorizationUri)
      dest.writeString(userCode)
      dest.writeString(requestCode)
      dest.writeLong(interval)
      dest.writeLong(lastPoll)
    }

    companion object {
      @JvmField
      val CREATOR: Parcelable.Creator<RequestState> =
          object : Parcelable.Creator<RequestState> {
            override fun createFromParcel(parcel: Parcel): RequestState {
              return RequestState(parcel)
            }

            override fun newArray(size: Int): Array<RequestState?> {
              return arrayOfNulls(size)
            }
          }
    }
  }

  companion object {
    @VisibleForTesting internal val DEVICE_LOGIN_ENDPOINT = "device/login"

    @VisibleForTesting internal val DEVICE_LOGIN_STATUS_ENDPOINT = "device/login_status"
    private const val REQUEST_STATE_KEY = "request_state"
    private const val LOGIN_ERROR_SUBCODE_EXCESSIVE_POLLING = 1349172
    private const val LOGIN_ERROR_SUBCODE_AUTHORIZATION_DECLINED = 1349173

    @VisibleForTesting internal val LOGIN_ERROR_SUBCODE_AUTHORIZATION_PENDING = 1349174
    private const val LOGIN_ERROR_SUBCODE_CODE_EXPIRED = 1349152

    @Throws(JSONException::class)
    private fun handlePermissionResponse(result: JSONObject): PermissionsLists {
      val permissions = result.getJSONObject("permissions")
      val data = permissions.getJSONArray("data")
      val grantedPermissions = arrayListOf<String>()
      val declinedPermissions = arrayListOf<String>()
      val expiredPermissions = arrayListOf<String>()
      for (i in 0 until data.length()) {
        val obj = data.optJSONObject(i)
        val permission = obj.optString("permission")
        if (permission.isEmpty() || permission == "installed") {
          continue
        }
        when (obj.optString("status")) {
          "granted" -> {
            grantedPermissions.add(permission)
          }
          "declined" -> {
            declinedPermissions.add(permission)
          }
          "expired" -> {
            expiredPermissions.add(permission)
          }
          else -> continue
        }
      }
      return PermissionsLists(grantedPermissions, declinedPermissions, expiredPermissions)
    }
  }

  /**
   * Internal helper class that is used to hold three different permission lists (granted, declined
   * and expired)
   */
  private class PermissionsLists(
      var grantedPermissions: List<String>,
      var declinedPermissions: List<String>,
      var expiredPermissions: List<String>
  )
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

import android.os.Parcel
import android.os.Parcelable
import androidx.annotation.RestrictTo
import com.facebook.AccessToken
import com.facebook.AccessTokenSource
import java.lang.Exception
import java.util.Date
import java.util.concurrent.ScheduledThreadPoolExecutor

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
open class DeviceAuthMethodHandler : LoginMethodHandler {
  constructor(loginClient: LoginClient) : super(loginClient)
  protected constructor(parcel: Parcel) : super(parcel)

  override fun tryAuthorize(request: LoginClient.Request): Int {
    showDialog(request)
    return 1
  }

  private fun showDialog(request: LoginClient.Request) {
    val activity = loginClient.activity
    if (activity == null || activity.isFinishing) {
      return
    }
    val dialog = createDeviceAuthDialog()
    dialog.show(activity.supportFragmentManager, "login_with_facebook")
    dialog.startLogin(request)
  }

  protected open fun createDeviceAuthDialog(): DeviceAuthDialog {
    return DeviceAuthDialog()
  }

  /** Invoke it when the user cancels the login. */
  open fun onCancel() {
    val outcome =
        LoginClient.Result.createCancelResult(loginClient.pendingRequest, "User canceled log in.")
    loginClient.completeAndValidate(outcome)
  }

  /** Invoke it when an error is received. */
  open fun onError(ex: Exception) {
    val outcome = LoginClient.Result.createErrorResult(loginClient.pendingRequest, null, ex.message)
    loginClient.completeAndValidate(outcome)
  }

  /** Invoke it when the login flow succeeds and the access token is returned. */
  open fun onSuccess(
      accessToken: String,
      applicationId: String,
      userId: String,
      permissions: Collection<String?>?,
      declinedPermissions: Collection<String?>?,
      expiredPermissions: Collection<String?>?,
      accessTokenSource: AccessTokenSource?,
      expirationTime: Date?,
      lastRefreshTime: Date?,
      dataAccessExpirationTime: Date?
  ) {
    val token =
        AccessToken(
            accessToken,
            applicationId,
            userId,
            permissions,
            declinedPermissions,
            expiredPermissions,
            accessTokenSource,
            expirationTime,
            lastRefreshTime,
            dataAccessExpirationTime)
    val outcome = LoginClient.Result.createTokenResult(loginClient.pendingRequest, token)
    loginClient.completeAndValidate(outcome)
  }

  override val nameForLogging: String = "device_auth"

  override fun describeContents(): Int = 0

  companion object {
    private lateinit var backgroundExecutor: ScheduledThreadPoolExecutor

    @JvmStatic
    @Synchronized
    fun getBackgroundExecutor(): ScheduledThreadPoolExecutor {
      if (!this::backgroundExecutor.isInitialized) {
        backgroundExecutor = ScheduledThreadPoolExecutor(1)
      }
      return backgroundExecutor
    }

    @JvmField
    val CREATOR: Parcelable.Creator<DeviceAuthMethodHandler> =
        object : Parcelable.Creator<DeviceAuthMethodHandler> {
          override fun createFromParcel(source: Parcel): DeviceAuthMethodHandler {
            return DeviceAuthMethodHandler(source)
          }

          override fun newArray(size: Int): Array<DeviceAuthMethodHandler?> {
            return arrayOfNulls(size)
          }
        }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

import android.content.Context
import android.os.Bundle
import com.facebook.internal.NativeProtocol
import com.facebook.internal.PlatformServiceClient

internal class GetTokenClient(context: Context, request: LoginClient.Request) :
    PlatformServiceClient(
        context,
        NativeProtocol.MESSAGE_GET_ACCESS_TOKEN_REQUEST,
        NativeProtocol.MESSAGE_GET_ACCESS_TOKEN_REPLY,
        NativeProtocol.PROTOCOL_VERSION_20121101,
        request.applicationId,
        request.nonce) {
  override fun populateRequestBundle(data: Bundle) = Unit
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import android.text.TextUtils
import com.facebook.AccessTokenSource
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.facebook.internal.NativeProtocol
import com.facebook.internal.PlatformServiceClient
import com.facebook.internal.Utility
import com.facebook.internal.Utility.getGraphMeRequestWithCacheAsync
import java.util.HashSet
import org.json.JSONException
import org.json.JSONObject

class GetTokenLoginMethodHandler : LoginMethodHandler {
  private var getTokenClient: GetTokenClient? = null

  constructor(loginClient: LoginClient) : super(loginClient)

  override val nameForLogging = "get_token"

  override fun cancel() {
    getTokenClient?.let {
      it.cancel()
      it.setCompletedListener(null)
      getTokenClient = null
    }
  }

  override fun tryAuthorize(request: LoginClient.Request): Int {
    getTokenClient =
        GetTokenClient(loginClient.activity ?: FacebookSdk.getApplicationContext(), request)
    if (getTokenClient?.start() == false) {
      return 0
    }
    loginClient.notifyBackgroundProcessingStart()
    val callback =
        PlatformServiceClient.CompletedListener { result -> getTokenCompleted(request, result) }
    getTokenClient?.setCompletedListener(callback)
    return 1
  }

  fun getTokenCompleted(request: LoginClient.Request, result: Bundle?) {
    getTokenClient?.setCompletedListener(null)
    getTokenClient = null
    loginClient.notifyBackgroundProcessingStop()
    if (result != null) {
      val currentPermissions =
          result.getStringArrayList(NativeProtocol.EXTRA_PERMISSIONS) ?: emptyList()
      val permissions = request.permissions ?: emptySet()
      val idTokenString = result.getString(NativeProtocol.EXTRA_AUTHENTICATION_TOKEN)

      // if request param has openid but result does not have id_token
      // fallback to try next handler to get id_token
      if (permissions.contains("openid") && idTokenString.isNullOrEmpty()) {
        loginClient.tryNextHandler()
        return
      }
      if (currentPermissions.containsAll(permissions)) {
        // We got all the permissions we needed, so we can complete the auth now.
        complete(request, result)
        return
      }

      // We didn't get all the permissions we wanted, so update the request with just the
      // permissions we still need.
      val newPermissions = HashSet<String>()
      for (permission in permissions) {
        if (!currentPermissions.contains(permission)) {
          newPermissions.add(permission)
        }
      }
      if (newPermissions.isNotEmpty()) {
        addLoggingExtra(
            LoginLogger.EVENT_EXTRAS_NEW_PERMISSIONS, TextUtils.join(",", newPermissions))
      }
      request.permissions = newPermissions
    }
    loginClient.tryNextHandler()
  }

  fun onComplete(request: LoginClient.Request, result: Bundle) {
    val outcome =
        try {
          val token =
              createAccessTokenFromNativeLogin(
                  result, AccessTokenSource.FACEBOOK_APPLICATION_SERVICE, request.applicationId)
          val authenticationToken = createAuthenticationTokenFromNativeLogin(result, request.nonce)
          LoginClient.Result.createCompositeTokenResult(request, token, authenticationToken)
        } catch (ex: FacebookException) {
          LoginClient.Result.createErrorResult(loginClient.pendingRequest, null, ex.message)
        }
    loginClient.completeAndValidate(outcome)
  }

  // Workaround for old facebook apps that don't return the userid.
  fun complete(request: LoginClient.Request, result: Bundle) {
    val userId = result.getString(NativeProtocol.EXTRA_USER_ID)
    // If the result is missing the UserId request it
    if (userId.isNullOrEmpty()) {
      loginClient.notifyBackgroundProcessingStart()
      val accessToken = checkNotNull(result.getString(NativeProtocol.EXTRA_ACCESS_TOKEN))
      getGraphMeRequestWithCacheAsync(
          accessToken,
          object : Utility.GraphMeRequestWithCacheCallback {
            override fun onSuccess(userInfo: JSONObject?) {
              try {
                result.putString(NativeProtocol.EXTRA_USER_ID, userInfo?.getString("id"))
                onComplete(request, result)
              } catch (ex: JSONException) {
                loginClient.complete(
                    LoginClient.Result.createErrorResult(
                        loginClient.pendingRequest, "Caught exception", ex.message))
              }
            }

            override fun onFailure(error: FacebookException?) {
              loginClient.complete(
                  LoginClient.Result.createErrorResult(
                      loginClient.pendingRequest, "Caught exception", error?.message))
            }
          })
    } else {
      onComplete(request, result)
    }
  }

  constructor(source: Parcel) : super(source)

  override fun describeContents(): Int = 0

  companion object {
    @JvmField
    val CREATOR: Parcelable.Creator<GetTokenLoginMethodHandler> =
        object : Parcelable.Creator<GetTokenLoginMethodHandler> {
          override fun createFromParcel(source: Parcel): GetTokenLoginMethodHandler {
            return GetTokenLoginMethodHandler(source)
          }

          override fun newArray(size: Int): Array<GetTokenLoginMethodHandler?> {
            return arrayOfNulls(size)
          }
        }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

import android.os.Parcel
import android.os.Parcelable
import com.facebook.AccessTokenSource
import com.facebook.FacebookSdk
import com.facebook.internal.NativeProtocol
import com.facebook.internal.ServerProtocol

internal class InstagramAppLoginMethodHandler : NativeAppLoginMethodHandler {

  constructor(loginClient: LoginClient) : super(loginClient)
  override val nameForLogging = "instagram_login"

  override val tokenSource: AccessTokenSource = AccessTokenSource.INSTAGRAM_APPLICATION_WEB

  override fun tryAuthorize(request: LoginClient.Request): Int {
    val e2e = LoginClient.getE2E()
    val intent =
        NativeProtocol.createInstagramIntent(
            loginClient.activity ?: FacebookSdk.getApplicationContext(),
            request.applicationId,
            request.permissions,
            e2e,
            request.isRerequest,
            request.hasPublishPermission(),
            request.defaultAudience ?: DefaultAudience.NONE,
            getClientState(request.authId),
            request.authType,
            request.messengerPageId,
            request.resetMessengerState,
            request.isFamilyLogin,
            request.shouldSkipAccountDeduplication())

    addLoggingExtra(ServerProtocol.DIALOG_PARAM_E2E, e2e)

    val result = tryIntent(intent, LoginClient.getLoginRequestCode())
    return if (result) 1 else 0
  }

  constructor(source: Parcel) : super(source)

  override fun describeContents(): Int = 0

  override fun writeToParcel(dest: Parcel, flags: Int) {
    super.writeToParcel(dest, flags)
  }

  companion object {
    @JvmField
    val CREATOR =
        object : Parcelable.Creator<InstagramAppLoginMethodHandler> {
          override fun createFromParcel(source: Parcel): InstagramAppLoginMethodHandler {
            return InstagramAppLoginMethodHandler(source)
          }

          override fun newArray(size: Int): Array<InstagramAppLoginMethodHandler?> {
            return arrayOfNulls(size)
          }
        }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

import android.os.Parcel
import android.os.Parcelable
import androidx.annotation.VisibleForTesting
import com.facebook.FacebookSdk
import com.facebook.internal.CustomTabUtils.getChromePackage
import com.facebook.internal.NativeProtocol.createProxyAuthIntents
import com.facebook.internal.ServerProtocol

@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
class KatanaProxyLoginMethodHandler : NativeAppLoginMethodHandler {
  constructor(loginClient: LoginClient) : super(loginClient)

  override val nameForLogging = "katana_proxy_auth"

  override fun tryAuthorize(request: LoginClient.Request): Int {
    val behavior = request.loginBehavior
    val ignoreAppSwitchToLoggedOut =
        (FacebookSdk.ignoreAppSwitchToLoggedOut &&
            getChromePackage() != null &&
            behavior.allowsCustomTabAuth())
    val e2e = LoginClient.getE2E()
    val intents =
        createProxyAuthIntents(
            loginClient.activity,
            request.applicationId,
            request.permissions,
            e2e,
            request.isRerequest,
            request.hasPublishPermission(),
            request.defaultAudience ?: DefaultAudience.NONE,
            getClientState(request.authId),
            request.authType,
            ignoreAppSwitchToLoggedOut,
            request.messengerPageId,
            request.resetMessengerState,
            request.isFamilyLogin,
            request.shouldSkipAccountDeduplication(),
            request.nonce,
            request.codeChallenge,
            request.codeChallengeMethod?.name)
    addLoggingExtra(ServerProtocol.DIALOG_PARAM_E2E, e2e)
    for ((i, intent) in intents.withIndex()) {
      val launchedIntent = tryIntent(intent, LoginClient.getLoginRequestCode())
      if (launchedIntent) {
        return i + 1
      }
    }
    return 0
  }

  override fun shouldKeepTrackOfMultipleIntents(): Boolean = true

  constructor(source: Parcel) : super(source)

  override fun describeContents(): Int = 0

  companion object {
    @JvmField
    val CREATOR: Parcelable.Creator<KatanaProxyLoginMethodHandler> =
        object : Parcelable.Creator<KatanaProxyLoginMethodHandler> {
          override fun createFromParcel(source: Parcel): KatanaProxyLoginMethodHandler {
            return KatanaProxyLoginMethodHandler(source)
          }

          override fun newArray(size: Int): Array<KatanaProxyLoginMethodHandler?> {
            return arrayOfNulls(size)
          }
        }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

/** Specifies the behaviors to try during login. */
enum class LoginBehavior {
  /**
   * Specifies that login should attempt login in using a native app in the Facebook Family of Apps,
   * and if that does not work, fall back to web dialog auth. This is the default behavior.
   */
  NATIVE_WITH_FALLBACK(true, true, true, false, true, true, true),

  /**
   * Specifies that login should only attempt to login using a native app in the Facebook Family of
   * Apps. If the native app cannot be used, then the login fails.
   */
  NATIVE_ONLY(true, true, false, false, false, true, true),

  /** Specifies that login should only attempt to use Katana Proxy Login. */
  KATANA_ONLY(false, true, false, false, false, false, false),

  /** Specifies that only the web dialog auth should be used. */
  WEB_ONLY(false, false, true, false, true, false, false),

  /** Specifies that only the web dialog auth (from anywhere) should be used */
  DIALOG_ONLY(false, true, true, false, true, true, true),

  /**
   * Specifies that device login authentication flow should be used. Use it via ({@link
   * com.facebook.login.widget.DeviceLoginButton DeviceLoginButton} or ({@link
   * com.facebook.login.DeviceLoginManager DeviceLoginManager} to authenticate.
   */
  DEVICE_AUTH(false, false, false, true, false, false, false);

  private val allowsGetTokenAuth: Boolean
  private val allowsKatanaAuth: Boolean
  private val allowsWebViewAuth: Boolean
  private val allowsDeviceAuth: Boolean
  private val allowsCustomTabAuth: Boolean
  private val allowsFacebookLiteAuth: Boolean
  private val allowsInstagramAppAuth: Boolean

  constructor(
      allowsGetTokenAuth: Boolean,
      allowsKatanaAuth: Boolean,
      allowsWebViewAuth: Boolean,
      allowsDeviceAuth: Boolean,
      allowsCustomTabAuth: Boolean,
      allowsFacebookLiteAuth: Boolean,
      allowsInstagramAppAuth: Boolean
  ) {
    this.allowsGetTokenAuth = allowsGetTokenAuth
    this.allowsKatanaAuth = allowsKatanaAuth
    this.allowsWebViewAuth = allowsWebViewAuth
    this.allowsDeviceAuth = allowsDeviceAuth
    this.allowsCustomTabAuth = allowsCustomTabAuth
    this.allowsFacebookLiteAuth = allowsFacebookLiteAuth
    this.allowsInstagramAppAuth = allowsInstagramAppAuth
  }

  /** Note: This getter is for internal only */
  fun allowsGetTokenAuth(): Boolean = allowsGetTokenAuth

  /** Note: This getter is for internal only */
  fun allowsKatanaAuth(): Boolean = allowsKatanaAuth

  /** Note: This getter is for internal only */
  fun allowsWebViewAuth(): Boolean = allowsWebViewAuth

  /** Note: This getter is for internal only */
  fun allowsDeviceAuth(): Boolean = allowsDeviceAuth

  /** Note: This getter is for internal only */
  fun allowsCustomTabAuth(): Boolean = allowsCustomTabAuth

  /** Note: This getter is for internal only */
  fun allowsFacebookLiteAuth(): Boolean = allowsFacebookLiteAuth

  /** Note: This getter is for internal only */
  fun allowsInstagramAppAuth(): Boolean = allowsInstagramAppAuth
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Parcel
import android.os.Parcelable
import android.text.TextUtils
import androidx.annotation.RestrictTo
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.facebook.AccessToken
import com.facebook.AuthenticationToken
import com.facebook.CustomTabMainActivity
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.facebook.appevents.AppEventsConstants
import com.facebook.common.R
import com.facebook.internal.CallbackManagerImpl
import com.facebook.internal.Utility.readNonnullStringMapFromParcel
import com.facebook.internal.Utility.writeNonnullStringMapToParcel
import com.facebook.internal.Validate
import java.lang.Exception
import java.util.UUID
import kotlin.jvm.JvmOverloads
import org.json.JSONException
import org.json.JSONObject

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
open class LoginClient : Parcelable {
  var handlersToTry: Array<LoginMethodHandler>? = null
  private var currentHandler = -1
  var fragment: Fragment? = null
    set(value) {
      if (field != null) {
        throw FacebookException("Can't set fragment once it is already set.")
      }
      field = value
    }
  var onCompletedListener: OnCompletedListener? = null
  var backgroundProcessingListener: BackgroundProcessingListener? = null
  var checkedInternetPermission = false
  var pendingRequest: Request? = null
  var loggingExtras: MutableMap<String, String>? = null
  var extraData: MutableMap<String, String>? = null
  private var loginLogger: LoginLogger? = null
  private var numActivitiesReturned = 0
  private var numTotalIntentsFired = 0

  fun interface OnCompletedListener {
    fun onCompleted(result: Result)
  }

  interface BackgroundProcessingListener {
    fun onBackgroundProcessingStarted()
    fun onBackgroundProcessingStopped()
  }

  constructor(fragment: Fragment) {
    this.fragment = fragment
  }

  val activity: FragmentActivity?
    get() = fragment?.activity

  fun startOrContinueAuth(request: Request?) {
    if (!inProgress) {
      authorize(request)
    }
  }

  fun authorize(request: Request?) {
    if (request == null) {
      return
    }
    if (pendingRequest != null) {
      throw FacebookException("Attempted to authorize while a request is pending.")
    }
    if (AccessToken.isCurrentAccessTokenActive() && !checkInternetPermission()) {
      // We're going to need INTERNET permission later and don't have it, so fail early.
      return
    }
    pendingRequest = request
    handlersToTry = getHandlersToTry(request)
    tryNextHandler()
  }

  val inProgress: Boolean
    get() = pendingRequest != null && currentHandler >= 0

  fun cancelCurrentHandler() {
    getCurrentHandler()?.cancel()
  }

  fun getCurrentHandler(): LoginMethodHandler? {
    return if (currentHandler >= 0) {
      handlersToTry?.get(currentHandler)
    } else {
      null
    }
  }

  /** Only for internal use when the class is restored from serialized parcel. */
  protected fun setCurrentHandlerIndex(index: Int) {
    currentHandler = index
  }

  fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
    numActivitiesReturned++
    if (pendingRequest != null) {
      if (data != null) {
        // If CustomTabs throws ActivityNotFoundException, then we would eventually return here.
        // In that case, we should treat that as tryAuthorize and try the next handler
        val hasNoBrowserException =
            data.getBooleanExtra(CustomTabMainActivity.NO_ACTIVITY_EXCEPTION, false)
        if (hasNoBrowserException) {
          tryNextHandler()
          return false
        }
      }
      val currentHandler = getCurrentHandler()
      if ((currentHandler != null) &&
          (!currentHandler.shouldKeepTrackOfMultipleIntents() ||
              data != null ||
              numActivitiesReturned >= numTotalIntentsFired)) {
        return currentHandler.onActivityResult(requestCode, resultCode, data)
      }
    }
    return false
  }

  open fun getHandlersToTry(request: Request): Array<LoginMethodHandler>? {
    val handlers = ArrayList<LoginMethodHandler>()
    val behavior = request.loginBehavior
    if (request.isInstagramLogin) {
      if (!FacebookSdk.bypassAppSwitch && behavior.allowsInstagramAppAuth()) {
        handlers.add(InstagramAppLoginMethodHandler(this))
      }
    } else {
      // Only use get token auth and native FB4A auth for FB logins
      if (behavior.allowsGetTokenAuth()) {
        handlers.add(GetTokenLoginMethodHandler(this))
      }
      if (!FacebookSdk.bypassAppSwitch && behavior.allowsKatanaAuth()) {
        handlers.add(KatanaProxyLoginMethodHandler(this))
      }
    }
    if (behavior.allowsCustomTabAuth()) {
      handlers.add(CustomTabLoginMethodHandler(this))
    }
    if (behavior.allowsWebViewAuth()) {
      handlers.add(WebViewLoginMethodHandler(this))
    }
    if (!request.isInstagramLogin && behavior.allowsDeviceAuth()) {
      handlers.add(DeviceAuthMethodHandler(this))
    }
    return handlers.toTypedArray()
  }

  fun checkInternetPermission(): Boolean {
    if (checkedInternetPermission) {
      return true
    }
    val permissionCheck = checkPermission(Manifest.permission.INTERNET)
    if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
      val activity: Activity? = activity
      val errorType = activity?.getString(R.string.com_facebook_internet_permission_error_title)
      val errorDescription =
          activity?.getString(R.string.com_facebook_internet_permission_error_message)
      complete(Result.createErrorResult(pendingRequest, errorType, errorDescription))
      return false
    }
    checkedInternetPermission = true
    return true
  }

  fun tryNextHandler() {
    val currentHandlerObject = getCurrentHandler()
    if (currentHandlerObject != null) {
      logAuthorizationMethodComplete(
          currentHandlerObject.nameForLogging,
          LoginLogger.EVENT_PARAM_METHOD_RESULT_SKIPPED,
          null,
          null,
          currentHandlerObject.methodLoggingExtras)
    }
    val handlersToTry = handlersToTry
    while (handlersToTry != null && currentHandler < handlersToTry.size - 1) {
      currentHandler++
      val started = tryCurrentHandler()
      if (started) {
        return
      }
    }
    if (pendingRequest != null) {
      // We went through all handlers without successfully attempting an auth.
      completeWithFailure()
    }
  }

  private fun completeWithFailure() {
    complete(Result.createErrorResult(pendingRequest, "Login attempt failed.", null))
  }

  private fun addLoggingExtra(key: String, value: String, accumulate: Boolean) {
    var value = value
    val extras = loggingExtras ?: HashMap()
    if (loggingExtras == null) {
      loggingExtras = extras
    }
    if (extras.containsKey(key) && accumulate) {
      value = "${extras[key]},$value"
    }
    extras[key] = value
  }

  fun addExtraData(key: String, value: String, accumulate: Boolean) {
    var value = value
    val extras = extraData ?: HashMap()
    if (extraData == null) {
      extraData = extras
    }
    if (extras.containsKey(key) && accumulate) {
      value = "${extras[key]},$value"
    }
    extras[key] = value
  }

  fun tryCurrentHandler(): Boolean {
    val handler = getCurrentHandler() ?: return false
    if (handler.needsInternetPermission() && !checkInternetPermission()) {
      addLoggingExtra(
          LoginLogger.EVENT_EXTRAS_MISSING_INTERNET_PERMISSION,
          AppEventsConstants.EVENT_PARAM_VALUE_YES,
          false)
      return false
    }
    val pendingRequest = pendingRequest ?: return false
    val numTried = handler.tryAuthorize(pendingRequest)
    numActivitiesReturned = 0
    if (numTried > 0) {
      logger.logAuthorizationMethodStart(
          pendingRequest.authId,
          handler.nameForLogging,
          if (pendingRequest.isFamilyLogin) LoginLogger.EVENT_NAME_FOA_LOGIN_METHOD_START
          else LoginLogger.EVENT_NAME_LOGIN_METHOD_START)
      numTotalIntentsFired = numTried
    } else {
      // We didn't try it, so we don't get any other completion
      // notification -- log that we skipped it.
      logger.logAuthorizationMethodNotTried(
          pendingRequest.authId,
          handler.nameForLogging,
          if (pendingRequest.isFamilyLogin) LoginLogger.EVENT_NAME_FOA_LOGIN_METHOD_NOT_TRIED
          else LoginLogger.EVENT_NAME_LOGIN_METHOD_NOT_TRIED)
      addLoggingExtra(LoginLogger.EVENT_EXTRAS_NOT_TRIED, handler.nameForLogging, true)
    }
    return numTried > 0
  }

  fun completeAndValidate(outcome: Result) {
    // Do we need to validate a successful result (as in the case of a reauth)?
    if (outcome.token != null && AccessToken.isCurrentAccessTokenActive()) {
      validateSameFbidAndFinish(outcome)
    } else {
      // We're done, just notify the listener.
      complete(outcome)
    }
  }

  fun complete(outcome: Result) {
    val handler = getCurrentHandler()

    // This might be null if, for some reason, none of the handlers were successfully tried
    // (in which case we already logged that).
    if (handler != null) {
      logAuthorizationMethodComplete(handler.nameForLogging, outcome, handler.methodLoggingExtras)
    }
    if (loggingExtras != null) {
      // Pass this back to the caller for logging at the aggregate level.
      outcome.loggingExtras = loggingExtras
    }
    if (extraData != null) {
      outcome.extraData = extraData
    }
    handlersToTry = null
    currentHandler = -1
    pendingRequest = null
    loggingExtras = null
    numActivitiesReturned = 0
    numTotalIntentsFired = 0
    notifyOnCompleteListener(outcome)
  }

  fun checkPermission(permission: String): Int {
    return activity?.checkCallingOrSelfPermission(permission) ?: PackageManager.PERMISSION_DENIED
  }

  fun validateSameFbidAndFinish(pendingResult: Result) {
    if (pendingResult.token == null) {
      throw FacebookException("Can't validate without a token")
    }
    val previousToken = AccessToken.getCurrentAccessToken()
    val newToken = pendingResult.token
    try {
      val result: Result
      if (previousToken != null && previousToken.userId == newToken.userId) {
        result =
            Result.createCompositeTokenResult(
                pendingRequest, pendingResult.token, pendingResult.authenticationToken)
      } else {
        result =
            Result.createErrorResult(
                pendingRequest, "User logged in as different Facebook user.", null)
      }
      complete(result)
    } catch (ex: Exception) {
      complete(Result.createErrorResult(pendingRequest, "Caught exception", ex.message))
    }
  }

  private val logger: LoginLogger
    get() {
      var loggerToReturn = loginLogger
      if (loggerToReturn == null || loggerToReturn.applicationId != pendingRequest?.applicationId) {
        loggerToReturn =
            LoginLogger(
                activity ?: FacebookSdk.getApplicationContext(),
                pendingRequest?.applicationId ?: FacebookSdk.getApplicationId())
        loginLogger = loggerToReturn
      }
      return loggerToReturn
    }

  private fun notifyOnCompleteListener(outcome: Result) {
    onCompletedListener?.onCompleted(outcome)
  }

  fun notifyBackgroundProcessingStart() {
    backgroundProcessingListener?.onBackgroundProcessingStarted()
  }

  fun notifyBackgroundProcessingStop() {
    backgroundProcessingListener?.onBackgroundProcessingStopped()
  }

  private fun logAuthorizationMethodComplete(
      method: String,
      result: Result,
      loggingExtras: Map<String?, String?>?
  ) {
    logAuthorizationMethodComplete(
        method, result.code.loggingValue, result.errorMessage, result.errorCode, loggingExtras)
  }

  private fun logAuthorizationMethodComplete(
      method: String,
      result: String,
      errorMessage: String?,
      errorCode: String?,
      loggingExtras: Map<String?, String?>?
  ) {
    val pendingRequest = pendingRequest
    if (pendingRequest == null) {
      // We don't expect this to happen, but if it does, log an event for diagnostic purposes.
      logger.logUnexpectedError(
          LoginLogger.EVENT_NAME_LOGIN_METHOD_COMPLETE,
          "Unexpected call to logCompleteLogin with null pendingAuthorizationRequest.",
          method)
    } else {
      logger.logAuthorizationMethodComplete(
          pendingRequest.authId,
          method,
          result,
          errorMessage,
          errorCode,
          loggingExtras,
          if (pendingRequest.isFamilyLogin) LoginLogger.EVENT_NAME_FOA_LOGIN_METHOD_COMPLETE
          else LoginLogger.EVENT_NAME_LOGIN_METHOD_COMPLETE)
    }
  }

  class Request : Parcelable {
    val loginBehavior: LoginBehavior
    var permissions: Set<String>
    val defaultAudience: DefaultAudience
    val applicationId: String
    var authId: String
    var isRerequest = false
    var deviceRedirectUriString: String? = null
    var authType: String
    var deviceAuthTargetUserId: String? = null // used to target a specific user with device login

    var messengerPageId: String? = null
    var resetMessengerState = false
    val loginTargetApp: LoginTargetApp
    var isFamilyLogin = false
    private var shouldSkipAccountDeduplication = false
    val nonce: String
    val codeVerifier: String?
    val codeChallenge: String?
    val codeChallengeMethod: CodeChallengeMethod?

    @JvmOverloads
    internal constructor(
        loginBehavior: LoginBehavior,
        permissions: Set<String>?,
        defaultAudience: DefaultAudience,
        authType: String,
        applicationId: String,
        authId: String,
        targetApp: LoginTargetApp? = LoginTargetApp.FACEBOOK,
        nonce: String? = null,
        codeVerifier: String? = null,
        codeChallenge: String? = null,
        codeChallengeMethod: CodeChallengeMethod? = null
    ) {
      this.loginBehavior = loginBehavior
      this.permissions = permissions ?: HashSet()
      this.defaultAudience = defaultAudience
      this.authType = authType
      this.applicationId = applicationId
      this.authId = authId
      loginTargetApp = targetApp ?: LoginTargetApp.FACEBOOK
      if (nonce == null || nonce.isEmpty()) {
        this.nonce = UUID.randomUUID().toString()
      } else {
        this.nonce = nonce
      }
      this.codeVerifier = codeVerifier
      this.codeChallenge = codeChallenge
      this.codeChallengeMethod = codeChallengeMethod
    }

    fun shouldSkipAccountDeduplication(): Boolean = shouldSkipAccountDeduplication

    fun setShouldSkipAccountDeduplication(shouldSkipAccountDeduplication: Boolean) {
      this.shouldSkipAccountDeduplication = shouldSkipAccountDeduplication
    }

    fun hasPublishPermission(): Boolean {
      for (permission in permissions) {
        if (LoginManager.isPublishPermission(permission)) {
          return true
        }
      }
      return false
    }

    val isInstagramLogin: Boolean
      get() = loginTargetApp === LoginTargetApp.INSTAGRAM

    private constructor(parcel: Parcel) {
      val loginBehaviorEnumValue = Validate.notNullOrEmpty(parcel.readString(), "loginBehavior")
      loginBehavior = LoginBehavior.valueOf(loginBehaviorEnumValue)
      val permissionsList = ArrayList<String>()
      parcel.readStringList(permissionsList)
      permissions = HashSet(permissionsList)
      val defaultAudienceEnumValue = parcel.readString()
      defaultAudience =
          if (defaultAudienceEnumValue != null) DefaultAudience.valueOf(defaultAudienceEnumValue)
          else DefaultAudience.NONE
      applicationId = Validate.notNullOrEmpty(parcel.readString(), "applicationId")
      authId = Validate.notNullOrEmpty(parcel.readString(), "authId")
      isRerequest = parcel.readByte().toInt() != 0
      deviceRedirectUriString = parcel.readString()
      authType = Validate.notNullOrEmpty(parcel.readString(), "authType")
      deviceAuthTargetUserId = parcel.readString()
      messengerPageId = parcel.readString()
      resetMessengerState = parcel.readByte().toInt() != 0
      val loginTargetAppEnumValue = parcel.readString()
      loginTargetApp =
          if (loginTargetAppEnumValue != null) LoginTargetApp.valueOf(loginTargetAppEnumValue)
          else LoginTargetApp.FACEBOOK
      isFamilyLogin = parcel.readByte().toInt() != 0
      shouldSkipAccountDeduplication = parcel.readByte().toInt() != 0
      nonce = Validate.notNullOrEmpty(parcel.readString(), "nonce")
      codeVerifier = parcel.readString()
      codeChallenge = parcel.readString()
      val codeChallengeMethodEnumVal = parcel.readString()
      codeChallengeMethod = codeChallengeMethodEnumVal?.let { CodeChallengeMethod.valueOf(it) }
    }

    override fun describeContents(): Int = 0

    override fun writeToParcel(dest: Parcel, flags: Int) {
      dest.writeString(loginBehavior.name)
      dest.writeStringList(ArrayList(permissions))
      dest.writeString(defaultAudience.name)
      dest.writeString(applicationId)
      dest.writeString(authId)
      dest.writeByte((if (isRerequest) 1 else 0).toByte())
      dest.writeString(deviceRedirectUriString)
      dest.writeString(authType)
      dest.writeString(deviceAuthTargetUserId)
      dest.writeString(messengerPageId)
      dest.writeByte((if (resetMessengerState) 1 else 0).toByte())
      dest.writeString(loginTargetApp.name)
      dest.writeByte((if (isFamilyLogin) 1 else 0).toByte())
      dest.writeByte((if (shouldSkipAccountDeduplication) 1 else 0).toByte())
      dest.writeString(nonce)
      dest.writeString(codeVerifier)
      dest.writeString(codeChallenge)
      dest.writeString(codeChallengeMethod?.name)
    }

    companion object {
      @JvmField
      val CREATOR: Parcelable.Creator<Request> =
          object : Parcelable.Creator<Request> {
            override fun createFromParcel(source: Parcel): Request {
              return Request(source)
            }

            override fun newArray(size: Int): Array<Request?> {
              return arrayOfNulls(size)
            }
          }
    }
  }

  class Result : Parcelable {
    enum class Code(
        // For consistency across platforms, we want to use specific string values when logging
        // these results.
        val loggingValue: String
    ) {
      SUCCESS("success"),
      CANCEL("cancel"),
      ERROR("error")
    }
    @JvmField val code: Code
    @JvmField val token: AccessToken?
    @JvmField val authenticationToken: AuthenticationToken?
    @JvmField val errorMessage: String?
    @JvmField val errorCode: String?
    @JvmField val request: Request?
    @JvmField var loggingExtras: Map<String, String>? = null
    @JvmField var extraData: Map<String, String>? = null

    internal constructor(
        request: Request?,
        code: Code,
        token: AccessToken?,
        errorMessage: String?,
        errorCode: String?
    ) : this(request, code, token, null, errorMessage, errorCode) {}

    internal constructor(
        request: Request?,
        code: Code,
        accessToken: AccessToken?,
        authenticationToken: AuthenticationToken?,
        errorMessage: String?,
        errorCode: String?
    ) {
      this.request = request
      token = accessToken
      this.authenticationToken = authenticationToken
      this.errorMessage = errorMessage
      this.code = code
      this.errorCode = errorCode
    }

    private constructor(parcel: Parcel) {
      this.code = Code.valueOf(parcel.readString() ?: "error")
      token = parcel.readParcelable(AccessToken::class.java.classLoader)
      authenticationToken = parcel.readParcelable(AuthenticationToken::class.java.classLoader)
      errorMessage = parcel.readString()
      errorCode = parcel.readString()
      request = parcel.readParcelable(Request::class.java.classLoader)
      loggingExtras = readNonnullStringMapFromParcel(parcel)
      extraData = readNonnullStringMapFromParcel(parcel)
    }

    override fun describeContents(): Int = 0

    override fun writeToParcel(dest: Parcel, flags: Int) {
      dest.writeString(code.name)
      dest.writeParcelable(token, flags)
      dest.writeParcelable(authenticationToken, flags)
      dest.writeString(errorMessage)
      dest.writeString(errorCode)
      dest.writeParcelable(request, flags)
      writeNonnullStringMapToParcel(dest, loggingExtras)
      writeNonnullStringMapToParcel(dest, extraData)
    }

    companion object {
      @JvmStatic
      fun createTokenResult(request: Request?, token: AccessToken): Result {
        return Result(request, Code.SUCCESS, token, null, null)
      }

      @JvmStatic
      fun createCompositeTokenResult(
          request: Request?,
          accessToken: AccessToken?,
          authenticationToken: AuthenticationToken?
      ): Result {
        return Result(request, Code.SUCCESS, accessToken, authenticationToken, null, null)
      }

      @JvmStatic
      fun createCancelResult(request: Request?, message: String?): Result {
        return Result(request, Code.CANCEL, null, message, null)
      }

      @JvmStatic
      @JvmOverloads
      fun createErrorResult(
          request: Request?,
          errorType: String?,
          errorDescription: String?,
          errorCode: String? = null
      ): Result {
        val messageParts = ArrayList<String?>()
        if (errorType != null) {
          messageParts.add(errorType)
        }
        if (errorDescription != null) {
          messageParts.add(errorDescription)
        }
        val message = TextUtils.join(": ", messageParts)
        return Result(request, Code.ERROR, null, message, errorCode)
      }

      @JvmField
      val CREATOR: Parcelable.Creator<Result> =
          object : Parcelable.Creator<Result> {
            override fun createFromParcel(source: Parcel): Result {
              return Result(source)
            }

            override fun newArray(size: Int): Array<Result?> {
              return arrayOfNulls(size)
            }
          }
    }
  }

  // Parcelable implementation
  constructor(source: Parcel) {
    val o: Array<Parcelable> =
        source.readParcelableArray(LoginMethodHandler::class.java.classLoader) ?: emptyArray()
    handlersToTry =
        o.mapNotNull {
              val handler = it as? LoginMethodHandler
              handler?.loginClient = this@LoginClient
              handler
            }
            .toTypedArray()
    currentHandler = source.readInt()
    pendingRequest = source.readParcelable(Request::class.java.classLoader)
    loggingExtras = readNonnullStringMapFromParcel(source)?.toMutableMap()
    extraData = readNonnullStringMapFromParcel(source)?.toMutableMap()
  }

  override fun describeContents(): Int = 0

  override fun writeToParcel(dest: Parcel, flags: Int) {
    dest.writeParcelableArray(handlersToTry, flags)
    dest.writeInt(currentHandler)
    dest.writeParcelable(pendingRequest, flags)
    writeNonnullStringMapToParcel(dest, loggingExtras)
    writeNonnullStringMapToParcel(dest, extraData)
  }

  companion object {
    @JvmStatic
    fun getLoginRequestCode(): Int = CallbackManagerImpl.RequestCodeOffset.Login.toRequestCode()

    @JvmStatic
    fun getE2E(): String {
      val e2e = JSONObject()
      try {
        e2e.put("init", System.currentTimeMillis())
      } catch (e: JSONException) {}
      return e2e.toString()
    }

    @JvmField
    val CREATOR: Parcelable.Creator<LoginClient> =
        object : Parcelable.Creator<LoginClient> {
          override fun createFromParcel(source: Parcel): LoginClient {
            return LoginClient(source)
          }

          override fun newArray(size: Int): Array<LoginClient?> {
            return arrayOfNulls(size)
          }
        }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

import java.util.Collections
import java.util.UUID
import kotlin.collections.HashSet

/** Configuration class to use for logging in with facebook */
class LoginConfiguration {

  /** The requested permissions for the login attempt. Defaults to an empty set. */
  val permissions: Set<String>

  /** he nonce that the configuration was created with. */
  val nonce: String

  /** PKCE code_verifier for getting the access_token and/or id_token */
  val codeVerifier: String

  /**
   * create a new configuration with the expected parameters.
   *
   * @param permissions the requested permissions for a login attempt. Permissions must be an array
   * of strings that do not contain whitespace.
   * @param nonce an optional nonce to use for the login attempt. A valid nonce must be a non-empty
   * string without whitespace. Creation of the configuration will fail if the nonce is invalid.
   */
  @JvmOverloads
  constructor(
      permissions: Collection<String>?,
      nonce: String = UUID.randomUUID().toString()
  ) : this(permissions, nonce, PKCEUtil.generateCodeVerifier())

  /**
   * create a new configuration with the expected parameters.
   *
   * @param permissions the requested permissions for a login attempt. Permissions must be an array
   * of strings that do not contain whitespace.
   * @param nonce an optional nonce to use for the login attempt. A valid nonce must be a non-empty
   * string without whitespace. Creation of the configuration will fail if the nonce is invalid.
   * @param codeVerifier a cryptographically random string using the characters A-Z, a-z, 0-9, and
   * the punctuation characters -._~ (hyphen, period, underscore, and tilde), between 43 and 128
   * characters long.
   */
  constructor(
      permissions: Collection<String>? = null,
      nonce: String = UUID.randomUUID().toString(),
      codeVerifier: String
  ) {
    require(NonceUtil.isValidNonce(nonce) && PKCEUtil.isValidCodeVerifier(codeVerifier))

    val permissions = if (permissions != null) HashSet(permissions) else HashSet()
    permissions.add(OPENID)
    this.permissions = Collections.unmodifiableSet(permissions)
    this.nonce = nonce
    this.codeVerifier = codeVerifier
  }

  companion object {
    const val OPENID = "openid"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.facebook.common.R

/**
 * This Fragment is a necessary part of the overall Facebook login process but is not meant to be
 * used directly.
 *
 * @see com.facebook.FacebookActivity
 */
open class LoginFragment : Fragment() {
  private var callingPackage: String? = null
  private var request: LoginClient.Request? = null
  lateinit var loginClient: LoginClient
    private set
  lateinit var launcher: ActivityResultLauncher<Intent>
    private set

  private lateinit var progressBar: View

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val restoredLoginClient = savedInstanceState?.getParcelable<LoginClient>(SAVED_LOGIN_CLIENT)
    loginClient =
        if (restoredLoginClient != null) {
          restoredLoginClient.fragment = this
          restoredLoginClient
        } else {
          createLoginClient()
        }
    loginClient.onCompletedListener =
        LoginClient.OnCompletedListener { outcome -> onLoginClientCompleted(outcome) }
    val activity = activity ?: return
    initializeCallingPackage(activity)
    val intent = activity.intent
    if (intent != null) {
      val bundle = intent.getBundleExtra(REQUEST_KEY)
      if (bundle != null) {
        request = bundle.getParcelable(EXTRA_REQUEST)
      }
    }

    launcher =
        registerForActivityResult(
            ActivityResultContracts.StartActivityForResult(),
            getLoginMethodHandlerCallback(activity))
  }

  private fun getLoginMethodHandlerCallback(activity: FragmentActivity): (ActivityResult) -> Unit =
      { result: ActivityResult ->
        if (result.resultCode == Activity.RESULT_OK) {
          loginClient.onActivityResult(
              LoginClient.getLoginRequestCode(), result.resultCode, result.data)
        } else {
          activity.finish()
        }
      }

  protected open fun createLoginClient(): LoginClient {
    return LoginClient(this)
  }

  override fun onDestroy() {
    loginClient.cancelCurrentHandler()
    super.onDestroy()
  }

  override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup?,
      savedInstanceState: Bundle?
  ): View? {
    val view = inflater.inflate(layoutResId, container, false)
    progressBar = view.findViewById<View>(R.id.com_facebook_login_fragment_progress_bar)
    loginClient.backgroundProcessingListener =
        object : LoginClient.BackgroundProcessingListener {
          override fun onBackgroundProcessingStarted() {
            showSpinner()
          }

          override fun onBackgroundProcessingStopped() {
            hideSpinner()
          }
        }
    return view
  }

  @get:LayoutRes
  protected open val layoutResId: Int
    get() = R.layout.com_facebook_login_fragment

  private fun onLoginClientCompleted(outcome: LoginClient.Result) {
    request = null
    val resultCode =
        if (outcome.code == LoginClient.Result.Code.CANCEL) Activity.RESULT_CANCELED
        else Activity.RESULT_OK
    val bundle = Bundle()
    bundle.putParcelable(RESULT_KEY, outcome)
    val resultIntent = Intent()
    resultIntent.putExtras(bundle)

    val hostActivity = activity
    // The activity might be detached we will send a cancel result in onDetach
    if (isAdded && hostActivity != null) {
      hostActivity.setResult(resultCode, resultIntent)
      hostActivity.finish()
    }
  }

  override fun onResume() {
    super.onResume()

    // If the calling package is null, this generally means that the callee was started
    // with a launchMode of singleInstance. Unfortunately, Android does not allow a result
    // to be set when the callee is a singleInstance, so we log an error and return.
    if (callingPackage == null) {
      Log.e(TAG, NULL_CALLING_PKG_ERROR_MSG)
      activity?.finish()
      return
    }
    loginClient.startOrContinueAuth(request)
  }

  override fun onPause() {
    super.onPause()
    val progressBar = view?.findViewById<View>(R.id.com_facebook_login_fragment_progress_bar)
    if (progressBar != null) {
      progressBar.visibility = View.GONE
    }
  }

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    loginClient.onActivityResult(requestCode, resultCode, data)
  }

  override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putParcelable(SAVED_LOGIN_CLIENT, loginClient)
  }

  private fun showSpinner() {
    progressBar.visibility = View.VISIBLE
    onSpinnerShown()
  }

  private fun hideSpinner() {
    progressBar.visibility = View.GONE
    onSpinnerHidden()
  }

  protected open fun onSpinnerShown() {}

  protected open fun onSpinnerHidden() {}

  private fun initializeCallingPackage(activity: Activity) {
    val componentName = activity.callingActivity ?: return
    callingPackage = componentName.packageName
  }

  companion object {
    internal const val RESULT_KEY = "com.facebook.LoginFragment:Result"
    const val REQUEST_KEY = "com.facebook.LoginFragment:Request"
    const val EXTRA_REQUEST = "request"
    private const val TAG = "LoginFragment"
    private const val NULL_CALLING_PKG_ERROR_MSG =
        "Cannot call LoginFragment with a null calling package. This can occur if the launchMode of the caller is singleInstance."
    private const val SAVED_LOGIN_CLIENT = "loginClient"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.login

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.text.TextUtils
import com.facebook.appevents.InternalAppEventsLogger
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.Exception
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.jvm.JvmOverloads
import org.json.JSONException
import org.json.JSONObject

@AutoHandleExceptions
internal class LoginLogger(context: Context, val applicationId: String) {
  private val logger: InternalAppEventsLogger = InternalAppEventsLogger(context, applicationId)
  private var facebookVersion: String? = null
  @JvmOverloads
  fun logStartLogin(
      pendingLoginRequest: LoginClient.Request,
      eventName: String? = EVENT_NAME_LOGIN_START
  ) {
    val bundle = newAuthorizationLoggingBundle(pendingLoginRequest.authId)

    // Log what we already know about the call in start event
    try {
      val extras = JSONObject()
      extras.put(EVENT_EXTRAS_LOGIN_BEHAVIOR, pendingLoginRequest.loginBehavior.toString())
      extras.put(EVENT_EXTRAS_REQUEST_CODE, LoginClient.getLoginRequestCode())
      extras.put(EVENT_EXTRAS_PERMISSIONS, TextUtils.join(",", pendingLoginRequest.permissions))
      extras.put(EVENT_EXTRAS_DEFAULT_AUDIENCE, pendingLoginRequest.defaultAudience.toString())
      extras.put(EVENT_EXTRAS_IS_REAUTHORIZE, pendingLoginRequest.isRerequest)
      if (facebookVersion != null) {
        extras.put(EVENT_EXTRAS_FACEBOOK_VERSION, facebookVersion)
      }
      if (pendingLoginRequest.loginTargetApp != null) {
        extras.put(EVENT_EXTRAS_TARGET_APP, pendingLoginRequest.loginTargetApp.toString())
      }
      bundle.putString(EVENT_PARAM_EXTRAS, extras.toString())
    } catch (e: JSONException) {}
    logger.logEventImplicitly(eventName, bundle)
  }

  @JvmOverloads
  fun logCompleteLogin(
      loginRequestId: String?,
      loggingExtras: Map<String, String>,
      result: LoginClient.Result.Code?,
      resultExtras: Map<String, String>?,
      exception: Exception?,
      eventName: String? = EVENT_NAME_LOGIN_COMPLETE
  ) {
    val bundle = newAuthorizationLoggingBundle(loginRequestId)
    if (result != null) {
      bundle.putString(EVENT_PARAM_LOGIN_RESULT, result.loggingValue)
    }
    if (exception?.message != null) {
      bundle.putString(EVENT_PARAM_ERROR_MESSAGE, exception.message)
    }

    // Combine extras from the request and from the result.
    var jsonObject: JSONObject? = null
    if (loggingExtras.isNotEmpty()) {
      jsonObject = JSONObject(loggingExtras)
    }
    if (resultExtras != null) {
      if (jsonObject == null) {
        jsonObject = JSONObject()
      }
      try {
        for ((key, value) in resultExtras) {
          if (key != null) {
            jsonObject.put(key, value)
          }
        }
      } catch (e: JSONException) {}
    }
    if (jsonObject != null) {
      bundle.putString(EVENT_PARAM_EXTRAS, jsonObject.toString())
    }
    logger.logEventImplicitly(eventName, bundle)
    if (result == LoginClient.Result.Code.SUCCESS) {
      logHeartbeatEvent(loginRequestId)
    }
  }

  private fun logHeartbeatEvent(loginRequestId: String?) {
    val bundle = newAuthorizationLoggingBundle(loginRequestId)
    val runnable = Runnable { logger.logEventImplicitly(EVENT_NAME_LOGIN_HEARTBEAT, bundle) }
    worker.schedule(runnable, 5, TimeUnit.SECONDS)
  }

  @JvmOverloads
  fun logAuthorizationMethodStart(
      authId: String?,
      method: String?,
      eventName: String? = EVENT_NAME_LOGIN_METHOD_START
  ) {
    val bundle = newAuthorizationLoggingBundle(authId)
    bundle.putString(EVENT_PARAM_METHOD, method)
    logger.logEventImplicitly(eventName, bundle)
  }

  @JvmOverloads
  fun logAuthorizationMethodComplete(
      authId: String?,
      method: String?,
      result: String?,
      errorMessage: String?,
      errorCode: String?,
      loggingExtras: Map<String?, String?>?,
      eventName: String? = EVENT_NAME_LOGIN_METHOD_COMPLETE
  ) {
    val bundle = newAuthorizationLoggingBundle(authId)
    if (result != null) {
      bundle.putString(EVENT_PARAM_LOGIN_RESULT, result)
    }
    if (errorMessage != null) {
      bundle.putString(EVENT_PARAM_ERROR_MESSAGE, errorMessage)
    }
    if (errorCode != null) {
      bundle.putString(EVENT_PARAM_ERROR_CODE, errorCode)
    }
    if (loggingExtras != null && loggingExtras.isNotEmpty()) {
      val jsonObject = JSONObject(loggingExtras.filterKeys { it != null })
      bundle.putString(EVENT_PARAM_EXTRAS, jsonObject.toString())
    }
    bundle.putString(EVENT_PARAM_METHOD, method)
    logger.logEventImplicitly(eventName, bundle)
  }

  @JvmOverloads
  fun logAuthorizationMethodNotTried(
      authId: String?,
      method: String?,
      eventName: String? = EVENT_NAME_LOGIN_METHOD_NOT_TRIED
  ) {
    val bundle = newAuthorizationLoggingBundle(authId)
    bundle.putString(EVENT_PARAM_METHOD, method)
    logger.logEventImplicitly(eventName, bundle)
  }

  fun logLoginStatusStart(loggerRef: String?) {
    val bundle = newAuthorizationLoggingBundle(loggerRef)
    logger.logEventImplicitly(EVENT_NAME_LOGIN_STATUS_START, bundle)
  }

  fun logLoginStatusSuccess(loggerRef: String?) {
    val bundle = newAuthorizationLoggingBundle(loggerRef)
    bundle.putString(EVENT_PARAM_LOGIN_RESULT, LoginClient.Result.Code.SUCCESS.loggingValue)
    logger.logEventImplicitly(EVENT_NAME_LOGIN_STATUS_COMPLETE, bundle)
  }

  fun logLoginStatusFailure(loggerRef: String?) {
    val bundle = newAuthorizationLoggingBundle(loggerRef)
    bundle.putString(EVENT_PARAM_LOGIN_RESULT, EVENT_EXTRAS_FAILURE)
    logger.logEventImplicitly(EVENT_NAME_LOGIN_STATUS_COMPLETE, bundle)
  }

  fun logLoginStatusError(loggerRef: String?, exception: Exception) {
    val bundle = newAuthorizationLoggingBundle(loggerRef)
    bundle.putString(EVENT_PARAM_LOGIN_RESULT, LoginClient.Result.Code.ERROR.loggingValue)
    bundle.putString(EVENT_PARAM_ERROR_MESSAGE, exception.toString())
    logger.logEventImplicitly(EVENT_NAME_LOGIN_STATUS_COMPLETE, bundle)
  }

  @JvmOverloads
  fun logUnexpectedError(eventName: String?, errorMessage: String?, method: String? = "") {
    val bundle = newAuthorizationLoggingBundle("")
    bundle.putString(EVENT_PARAM_LOGIN_RESULT, LoginClient.Result.Code.ERROR.loggingValue)
    bundle.putString(EVENT_PARAM_ERROR_MESSAGE, errorMessage)
    bundle.putString(EVENT_PARAM_METHOD, method)
    logger.logEventImplicitly(eventName, bundle)
  }

  companion object {
    // Constants for logging login-related data.
    const val EVENT_NAME_LOGIN_METHOD_START = "fb_mobile_login_method_start"
    const val EVENT_NAME_LOGIN_METHOD_COMPLETE = "fb_mobile_login_method_complete"
    const val EVENT_NAME_LOGIN_METHOD_NOT_TRIED = "fb_mobile_login_method_not_tried"
    const val EVENT_PARAM_METHOD_RESULT_SKIPPED = "skipped"
    const val EVENT_NAME_LOGIN_START = "fb_mobile_login_start"
    const val EVENT_NAME_LOGIN_COMPLETE = "fb_mobile_login_complete"
    const val EVENT_NAME_LOGIN_STATUS_START = "fb_mobile_login_status_start"
    const val EVENT_NAME_LOGIN_STATUS_COMPLETE = "fb_mobile_login_status_complete"
    const val EVENT_NAME_LOGIN_HEARTBEAT = "fb_mobile_login_heartbeat"
    const val EVENT_NAME_FOA_LOGIN_METHOD_START = "foa_mobile_login_method_start"
    const val EVENT_NAME_FOA_LOGIN_METHOD_COMPLETE = "foa_mobile_login_method_complete"
    const val EVENT_NAME_FOA_LOGIN_METHOD_NOT_TRIED = "foa_mobile_login_method_not_tried"
    const val EVENT_PARAM_FOA_METHOD_RESULT_SKIPPED = "foa_skipped"
    const val EVENT_NAME_FOA_LOGIN_START = "foa_mobile_login_start"
    const val EVENT_NAME_FOA_LOGIN_COMPLETE = "foa_mobile_login_complete"

    // Note: to ensure stability of column mappings across the four different event types, we
    // prepend a column index to each name, and we log all columns with all events, even if they are
    // empty.
    const val EVENT_PARAM_AUTH_LOGGER_ID = "0_auth_logger_id"
    const val EVENT_PARAM_TIMESTAMP = "1_timestamp_ms"
    const val EVENT_PARAM_LOGIN_RESULT = "2_result"
    const val EVENT_PARAM_METHOD = "3_method"
    const val EVENT_PARAM_ERROR_CODE = "4_error_code"
    const val EVENT_PARAM_ERROR_MESSAGE = "5_error_message"
    const val EVENT_PARAM_EXTRAS = "6_extras"
    const val EVENT_PARAM_CHALLENGE = "7_challenge"
    const val EVENT_EXTRAS_TRY_LOGIN_ACTIVITY = "try_login_activity"
    const val EVENT_EXTRAS_MISSING_INTERNET_PERMISSION = "no_internet_permission"
    const val EVENT_EXTRAS_NOT_TRIED = "not_tried"
    const val EVENT_EXTRAS_NEW_PERMISSIONS = "new_permissions"
    const val EVENT_EXTRAS_LOGIN_BEHAVIOR = "login_behavior"
    const val EVENT_EXTRAS_REQUEST_CODE = "request_code"
    const val EVENT_EXTRAS_PERMISSIONS = "permissions"
    const val EVENT_EXTRAS_DEFAULT_AUDIENCE = "default_audience"
    const val EVENT_EXTRAS_IS_REAUTHORIZE = "isReauthorize"
    const val EVENT_EXTRAS_FACEBOOK_VERSION = "facebookVersion"
    const val EVENT_EXTRAS_FAILURE = "failure"
    const val EVENT_EXTRAS_TARGET_APP = "target_app"
    const val FACEBOOK_PACKAGE_NAME = "com.facebook.katana"
    private val worker = Executors.newSingleThreadScheduledExecutor()

    private fun newAuthorizationLoggingBundle(authLoggerId: String?): Bundle {
      // We want to log all parameters for all events, to ensure stability of columns across
      // different event types.
      val bundle = Bundle()
      bundle.putLong(EVENT_PARAM_TIMESTAMP, System.currentTimeMillis())
      bundle.putString(EVENT_PARAM_AUTH_LOGGER_ID, authLoggerId)
      bundle.putString(EVENT_PARAM_METHOD, "")
      bundle.putString(EVENT_PARAM_LOGIN_RESULT, "")
      bundle.putString(EVENT_PARAM_ERROR_MESSAGE, "")
      bundle.putString(EVENT_PARAM_ERROR_CODE, "")
      bundle.putString(EVENT_PARAM_EXTRAS, "")
      return bundle
    }
  }

  init {
    // Store which version of facebook is installed
    try {
      val packageManager = context.packageManager
      if (packageManager != null) {
        val facebookInfo = packageManager.getPackageInfo(FACEBOOK_PACKAGE_NAME, 0)
        if (facebookInfo != null) {
          facebookVersion = facebookInfo.versionName
        }
      }
    } catch (e: PackageManager.NameNotFoundException) {
      // Do nothing, just ignore and not log
    }
  }
}