package net.pokeranalytics.android.util.billing

import android.app.Activity
import android.content.Context
import com.android.billingclient.api.*
import net.pokeranalytics.android.R
import timber.log.Timber
import java.io.IOException
import java.util.*
import kotlin.collections.ArrayList

enum class IAPProducts(var identifier: String) {
	PRO("unlimited")
}

interface PurchaseDelegate {
	fun purchaseDidSucceed(purchase: Purchase)
}

/**
 * the AppGuard object is in charge of contacting the Billing services to retrieve products,
 * initiating purchases and verifying transactions
 * Requests performed with the BillingClient must be done while being connected.
 * Use executeServiceRequest to ensure this
 */
object AppGuard : PurchasesUpdatedListener {

	private const val BASE_64_ENCODED_PUBLIC_KEY =
		"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0VuL5bk2w0FNaZIwE3v2857ZxJlo0epIIsVJfVs7Pqh7zE1JN7uGBgZ/r2s5Rn3o0R1Ycqxp832kYg/B0FvlQJ3Ko6IZkoyfJQ3i1zuAcc7NLMxKUMJY8Mc0U6Go2bevjQ54WkvumIdAFWIlMjyuOOFcSZRZr8V7tlq0SYlenkuellQeHIq3V47M/0jlDrEbCFj59hsukN75eGIiafFAxBYO/8L/flkZLik8YyhV1uZTu+KziA0PsbIvXKyN+gCK9UmrscTyM4+hfmRgb74fro67UsEqq2OvmHFUhubPzCZDElOwPeauUDEGeQjJn43iUHZWIcSEVktVB9cFa/0JwIDAQAB"

	const val MAX_SESSIONS_BEFORE_REQUESTING_SUBSCRIPTION: Int = 10

	/**
	 * The Billing Client making requests with Google Billing services
	 */
	private lateinit var billingClient: BillingClient

	/**
	 * Whether the billing client is available
	 */
	private var billingClientAvailable: Boolean = false

	/**
	 * Returns whether the user has the pro subscription
	 */
	var isProUser: Boolean = false
		private set

	/**
	 * A delegate to notify when the purchase has succeeded
	 */
	private var purchaseDelegate: PurchaseDelegate? = null

	/**
	 * Initialization of AppGuard
	 * Connects to billing services and restores purchases
	 */
	fun load(context: Context) {

		billingClient = BillingClient.newBuilder(context).setListener(this).build()

		this.startConnection(Runnable {
			this.updatePurchases()
		})

	}

	private fun startConnection(executeOnSuccess: Runnable) {

		billingClient.startConnection(object : BillingClientStateListener {
			override fun onBillingSetupFinished(@BillingClient.BillingResponse billingResponseCode: Int) {
				if (billingResponseCode == BillingClient.BillingResponse.OK) {
					// The BillingClient is ready. You can query purchases here.
					billingClientAvailable = true
					executeOnSuccess.run()
				}
			}

			override fun onBillingServiceDisconnected() {
				billingClientAvailable = false
			}
		})

	}

	private fun executeServiceRequest(runnable: Runnable) {

		if (billingClientAvailable) {
			runnable.run()
		} else {
			this.startConnection(runnable)
		}
	}

	fun requestPurchasesUpdate() {
		this.executeServiceRequest(Runnable {
			this.updatePurchases()
		})
	}

	/**
	 * Update the state of subscriptions
	 * Restores or stops access to IAPs
	 */
	private fun updatePurchases() {
		this.resetPurchases()
		// Automatically checks for purchases (when switching devices for example)
		val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS)
		if (purchasesResult != null) {
			purchasesResult.purchasesList.forEach {
				this.handlePurchase(it)
			}
		}
	}

	/**
	 * Reset all purchases
	 * This is done before restoring in order to ensure that subscriptions stops
	 */
	private fun resetPurchases() {
		this.isProUser = false
	}

	/**
	 * Requests the product descriptions
	 */
	fun requestProducts(listener: SkuDetailsResponseListener): Boolean {

		if (this.billingClientAvailable) {

			val skuList = ArrayList<String>()
			skuList.add(IAPProducts.PRO.identifier)
			val params = SkuDetailsParams.newBuilder()
			params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS)

			this.executeServiceRequest(Runnable {
				billingClient.querySkuDetailsAsync(params.build(), listener)
			})

			return true
		}
		return false
	}

	/**
	 * Initiates purchase with the product [skuDetails]
	 */
	fun initiatePurchase(activity: Activity, skuDetails: SkuDetails, delegate: PurchaseDelegate) {

		this.purchaseDelegate = delegate

		val flowParams = BillingFlowParams.newBuilder()
			.setSkuDetails(skuDetails)
			.build()

		this.executeServiceRequest(Runnable {
			val responseCode = billingClient.launchBillingFlow(activity, flowParams)
			Timber.d("launchBillingFlow returned $responseCode")
		})

	}

	// PurchasesUpdatedListener

	/**
	 * Purchase callback
	 */
	override fun onPurchasesUpdated(responseCode: Int, purchases: MutableList<Purchase>?) {

		if (responseCode == BillingClient.BillingResponse.OK && purchases != null) {
			for (purchase in purchases) {
				handlePurchase(purchase)
			}
		} else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) {
			// Handle an error caused by a user cancelling the purchase flow.
		} else {
			// Handle any other error codes.
		}

	}

	/**
	 * Method called when a [purchase] has been made
	 */
	private fun handlePurchase(purchase: Purchase) {

		if (this.verifyValidSignature(purchase.originalJson, purchase.signature)) {
			when (purchase.sku) {
				IAPProducts.PRO.identifier -> {


					val date = Date(purchase.purchaseTime)
					Timber.d("*** Auto renewing = ${purchase.isAutoRenewing}")
					Timber.d("*** purchaseTime = ${date}")

					this.isProUser = true
					this.purchaseDelegate?.let {
						it.purchaseDidSucceed(purchase)
						this.purchaseDelegate = null
					}
				}
				else -> {
				}
			}
		} else {
			// invalid purchase
		}

	}

	/**
	 * Verifies the validity of a purchase with its [signedData] and [signature]
	 */
	private fun verifyValidSignature(signedData: String, signature: String): Boolean {
		return try {
			Security.verifyPurchase(BASE_64_ENCODED_PUBLIC_KEY, signedData, signature)
		} catch (e: IOException) {
			Timber.d("Got an exception trying to validate a purchase: $e")
			false
		}
	}

	/**
	 * Returns the subscription status of the user, using the [context]
	 */
	fun subscriptionStatus(context: Context): String {
		val resId = if (this.isProUser) R.string.pro_sub_short_title else R.string.none
		return context.getString(resId)
	}

}