package com.adealink.weparty.wallet.pay

import android.app.Activity
import com.adealink.frame.base.IError
import com.adealink.frame.base.Rlt
import com.adealink.frame.coroutine.dispatcher.Dispatcher
import com.adealink.frame.frame.BaseFrame
import com.adealink.frame.log.Log
import com.adealink.frame.util.AppUtil
import com.adealink.weparty.module.account.AccountModule
import com.adealink.weparty.module.wallet.data.PayChannel
import com.adealink.weparty.module.wallet.data.PayChannelSetupError
import com.adealink.weparty.module.wallet.data.PurchaseInfo
import com.adealink.weparty.module.wallet.data.SkuInfo
import com.adealink.weparty.module.wallet.data.SkuQueryError
import com.adealink.weparty.wallet.data.TAG_GOOGLE_BILLING
import com.adealink.weparty.wallet.pay.model.GooglePurchase
import com.adealink.weparty.wallet.pay.model.GoogleSkuInfo
import com.adealink.weparty.wallet.pay.model.PurchaseResult
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.PendingPurchasesParams
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.QueryProductDetailsParams
import com.android.billingclient.api.QueryPurchasesParams
import com.android.billingclient.api.consumePurchase
import com.android.billingclient.api.queryProductDetails
import com.android.billingclient.api.queryPurchasesAsync
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.math.pow


class GoogleBilling : BaseFrame<IPayListener>(), IPay {

    /**
     * https://developer.android.com/reference/com/android/billingclient/api/BillingClient
     * 调试BillingClient对象: adb shell setprop log.tag.BillingClient VERBOSE
     */
    private var billingClient: BillingClient? = null

    private val _purchaseUpdates = MutableSharedFlow<PurchaseResult>(
        replay = 0,                         // 不要缓存旧值
        extraBufferCapacity = 1,            // 防止 emit 太快被丢弃
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    private val purchaseUpdates: SharedFlow<PurchaseResult> = _purchaseUpdates
    private var purchasingSkuIdSet: MutableSet<String> = mutableSetOf()

    private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
        Log.i(
            TAG_GOOGLE_BILLING,
            "purchaseUpdate, resultCode: ${billingResult.responseCode}, skuIds:${purchases?.map { it.products.getOrNull(0) ?: "" }},debugMessage:${billingResult.debugMessage}"
        )
        val resCode = getPayResCodeFromBillingResponse(billingResult.responseCode)
        val googlePurchaseInfo =
            PurchaseInfo(
                PayChannel.Google,
                purchases?.map { GooglePurchase(it) } ?: arrayListOf<GooglePurchase>()
            )
        launch {
            if (resCode == PayRes.SUCCESS) {
                //先处理延迟成功交易
                val pendingPurchase =
                    googlePurchaseInfo.purchases.filter { !purchasingSkuIdSet.contains(it.getSkuId()) }
                val pendingPurchaseInfo = PurchaseInfo(
                    PayChannel.Google,
                    pendingPurchase
                )
                dispatch {
                    it.onPendingPurchaseResult(
                        PurchaseResult(
                            resCode,
                            pendingPurchaseInfo
                        )
                    )
                }
            }
            _purchaseUpdates.emit(
                PurchaseResult(
                    resCode,
                    googlePurchaseInfo
                )
            )
        }
    }

    init {
        billingClient = BillingClient.newBuilder(AppUtil.appContext)
            .enablePendingPurchases(
                PendingPurchasesParams.newBuilder()
                    .enablePrepaidPlans()
                    .enableOneTimeProducts().build()
            )
            .setListener(purchasesUpdatedListener)
            .build()
    }

    private suspend fun ensureConnection(): BillingClient? {
        // 如果已经连接，直接返回
        billingClient?.takeIf { it.isReady }?.let { return it }

        // 否则建立新连接
        val rlt = setup()
        return when (rlt) {
            is Rlt.Failed -> {
                null
            }

            is Rlt.Success -> {
                billingClient
            }
        }
    }

    private suspend fun BillingClient.startConnection(): BillingResult = suspendCancellableCoroutine { continuation ->
        val alreadyResume = AtomicBoolean(false)
        startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                Log.i(
                    TAG_GOOGLE_BILLING,
                    "onBillingSetupFinished, resultCode: ${billingResult.responseCode}, debugMessage:${billingResult.debugMessage}"
                )
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    if (alreadyResume.compareAndSet(false, true) && continuation.isActive) {
                        Log.i(TAG_GOOGLE_BILLING, "Billing setup finished successfully.")
                        continuation.resume(billingResult)
                    }
                } else {
                    if (alreadyResume.compareAndSet(false, true) && continuation.isActive) {
                        Log.d(TAG_GOOGLE_BILLING, "Billing setup finished fail.")
                        continuation.resumeWithException(BillingConnectionException(billingResult))
                    }
                }
            }

            /**
             * https://developer.android.com/reference/com/android/billingclient/api/BillingClientStateListener#onBillingServiceDisconnected()
             *
             * Note: This does not remove the billing service connection itself - this binding to the service will remain active,
             * and you will receive a call to onBillingSetupFinished when the billing service is next running and setup is complete.
             */
            override fun onBillingServiceDisconnected() {
                // 这里不调用 continuation，因为这不是连接完成的回调
                Log.i(TAG_GOOGLE_BILLING, "onBillingServiceDisconnected")
            }
        })

        // 如果协程被取消，则结束连接
        continuation.invokeOnCancellation {
            endConnection()
        }
    }

    class BillingConnectionException(val billingResult: BillingResult) : Exception(billingResult.debugMessage)

    override suspend fun setup(): Rlt<Any> {
        return withContext(this.coroutineContext) {
            if (billingClient?.isReady == true) {
                return@withContext Rlt.Success(Any())
            }

            val maxRetries = 3
            var attempt = 0
            val retryDelay = 1000L // 1秒重试间隔
            var lastException: Exception? = null

            while (attempt < maxRetries) {
                try {
                    val client = BillingClient.newBuilder(AppUtil.appContext)
                        .enablePendingPurchases(
                            PendingPurchasesParams.newBuilder()
                                .enablePrepaidPlans()
                                .enableOneTimeProducts().build()
                        )
                        .setListener(purchasesUpdatedListener)
                        .build()

                    val result = client.startConnection()
                    if (result.responseCode == BillingClient.BillingResponseCode.OK) {
                        billingClient = client
                        return@withContext Rlt.Success(Any())
                    } else {
                        throw BillingConnectionException(result)
                    }
                } catch (e: Exception) {
                    lastException = e
                    attempt++
                    if (attempt < maxRetries) {
                        delay(retryDelay)
                    }
                }
            }

            return@withContext if (lastException is BillingConnectionException) {
                Rlt.Failed(
                    PayChannelSetupError(
                        code = getPayResCodeFromBillingResponse(lastException.billingResult.responseCode)
                    )
                )
            } else {
                Rlt.Failed(
                    PayChannelSetupError(
                        code = -1
                    )
                )
            }
        }
    }

    override suspend fun querySkuDetails(skuIdList: List<String>): Rlt<List<SkuInfo>> {
        return withContext(this.coroutineContext) {
            queryProductDetailsRetry(skuIdList)
        }
    }

    private suspend fun queryProductDetailsRetry(skuIdList: List<String>): Rlt<List<SkuInfo>> {
        val productList = skuIdList.map {
            QueryProductDetailsParams.Product.newBuilder()
                .setProductId(it)
                .setProductType(BillingClient.ProductType.INAPP)
                .build()
        }
        val params = QueryProductDetailsParams.newBuilder()
            .setProductList(productList)
            .build()

        // 最大重试次数
        val maxRetries = 3
        // 初始延迟时间（毫秒）
        val initialDelay = 1000L
        // 延迟倍数，用于指数回退
        val delayFactor = 2.0
        var attempt = 0

        while (true) {
            val client = ensureConnection()
            if (client == null || client.isReady.not()) {
                return Rlt.Failed(SkuQueryError(PayRes.SET_UP_DISCONNECTED))
            }
            val productDetailsResult = withContext(Dispatcher.WENEXT_THREAD_POOL) {
                client.queryProductDetails(params)
            }
            Log.i(
                TAG_GOOGLE_BILLING,
                "queryProductDetails, resultCode: ${productDetailsResult.billingResult.responseCode}, " +
                        "debugMessage:${productDetailsResult.billingResult.debugMessage}, " +
                        "skuDetailSize: ${productDetailsResult.productDetailsList?.size}"
            )

            when (productDetailsResult.billingResult.responseCode) {
                BillingClient.BillingResponseCode.OK -> {
                    if (productDetailsResult.productDetailsList.isNullOrEmpty()) {
                        return Rlt.Failed(SkuQueryError(PayRes.PURCHASE_SKU_UN_MATCH))
                    }
                    return Rlt.Success(productDetailsResult.productDetailsList!!.map {
                        GoogleSkuInfo(
                            skuDetails = it
                        )
                    })
                }

                in setOf(
                    BillingClient.BillingResponseCode.ERROR,
                    BillingClient.BillingResponseCode.NETWORK_ERROR,
                    BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
                    BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
                ) -> {
                    attempt++
                    if (attempt >= maxRetries) {
                        return Rlt.Failed(
                            SkuQueryError(getPayResCodeFromBillingResponse(productDetailsResult.billingResult.responseCode))
                        )
                    }
                    delay(initialDelay * delayFactor.pow(attempt.toDouble()).toLong())
                }

                else -> {
                    return Rlt.Failed(
                        SkuQueryError(getPayResCodeFromBillingResponse(productDetailsResult.billingResult.responseCode))
                    )
                }
            }
        }
    }

    override suspend fun consume(token: String): Rlt<Any?> {
        return withContext(this.coroutineContext) {
            consumeRetry(token)
        }
    }

    private suspend fun consumeRetry(token: String): Rlt<Any?> {
        // 最大重试次数
        val maxRetries = 3
        // 初始延迟时间（毫秒）
        val initialDelay = 1000L
        // 延迟倍数，用于指数回退
        val delayFactor = 2.0
        var attempt = 0

        val params = ConsumeParams.newBuilder()
            .setPurchaseToken(token)
            .build()
        while (true) {
            val client = ensureConnection()
            if (client == null || client.isReady.not()) {
                return Rlt.Failed(SkuQueryError(PayRes.SET_UP_DISCONNECTED))
            }

            val consumeResult = client.consumePurchase(params)

            val responseCode = consumeResult.billingResult.responseCode
            Log.i(
                TAG_GOOGLE_BILLING,
                "consumePurchase, resultCode: $responseCode, "
                        + "debugMessage:${consumeResult.billingResult.debugMessage}"
            )

            when (responseCode) {
                BillingClient.BillingResponseCode.OK -> {
                    return Rlt.Success(null)
                }

                in setOf(
                    BillingClient.BillingResponseCode.ERROR,
                    BillingClient.BillingResponseCode.NETWORK_ERROR,
                    BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
                    BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
                ) -> {
                    attempt++
                    if (attempt >= maxRetries) {
                        return Rlt.Failed(
                            IError(serverCode = getPayResCodeFromBillingResponse(responseCode))
                        )
                    }
                    delay(initialDelay * delayFactor.pow(attempt.toDouble()).toLong())
                }

                else -> {
                    return Rlt.Failed(
                        IError(serverCode = getPayResCodeFromBillingResponse(responseCode))
                    )
                }
            }
        }
    }

    override suspend fun launchPaymentFlow(
        activity: Activity,
        skuInfo: SkuInfo,
        orderId: String
    ): PurchaseResult {
        return withContext(this.coroutineContext) {
            launchPaymentFlowRetry(activity, skuInfo, orderId)
        }
    }

    private suspend fun launchPaymentFlowRetry(
        activity: Activity,
        skuInfo: SkuInfo,
        orderId: String
    ): PurchaseResult {
        // 最大重试次数
        val maxRetries = 3
        // 初始延迟时间（毫秒）
        val initialDelay = 1000L
        // 延迟倍数，用于指数回退
        val delayFactor = 2.0
        var attempt = 0

        val params = BillingFlowParams.newBuilder().apply {
            if (isFeatureSupported(BillingClient.FeatureType.PRODUCT_DETAILS)) {
                val skuDetails = (skuInfo as? GoogleSkuInfo)?.skuDetails
                    ?: return PurchaseResult(
                        resCode = PayRes.PURCHASE_SKU_UN_MATCH,
                        purchaseInfo = null
                    )
                setProductDetailsParamsList(
                    listOf(
                        BillingFlowParams.ProductDetailsParams.newBuilder()
                            .setProductDetails(skuDetails)
                            .build()
                    )
                )
            } else {
                val skuDetails = (skuInfo as? GoogleSkuInfo)?.skuDetailsOld
                    ?: return PurchaseResult(
                        resCode = PayRes.PURCHASE_SKU_UN_MATCH,
                        purchaseInfo = null
                    )
                setSkuDetails(skuDetails)
            }
            setObfuscatedAccountId(AccountModule.uid)
            setObfuscatedProfileId(orderId)
        }.build()

        while (true) {
            Log.i(TAG_GOOGLE_BILLING, "launchBillingFlow: skuId = ${skuInfo.getSkuId()}")
            val client = ensureConnection()
            if (client == null || client.isReady.not()) {
                return PurchaseResult(PayRes.SET_UP_DISCONNECTED, null)
            }
            purchasingSkuIdSet.add(skuInfo.getSkuId())
            val launchResult = withContext(Dispatcher.UI) {
                client.launchBillingFlow(activity, params)
            }

            val purchaseResult = when {
                launchResult.responseCode != BillingClient.BillingResponseCode.OK -> {
                    //发起支付流程不成功，可以直接重试或返回
                    PurchaseResult(
                        getPayResCodeFromBillingResponse(launchResult.responseCode),
                        null
                    )
                }

                else -> {
                    //等待当前支付结果
                    purchaseUpdates
                        .first { result ->
                            result.purchaseInfo?.purchases?.any { it.getSkuId() == skuInfo.getSkuId() } == true
                                    || result.resCode != BillingClient.BillingResponseCode.OK
                        }
                }
            }

            when (purchaseResult.resCode) {
                in setOf(
                    PayRes.ERROR,
                    PayRes.NETWORK_ERROR,
                    PayRes.SERVICE_DISCONNECTED,
                    PayRes.SERVICE_UNAVAILABLE
                ) -> {
                    attempt++
                    if (attempt >= maxRetries) {
                        purchasingSkuIdSet.remove(skuInfo.getSkuId())
                        return purchaseResult
                    }
                    delay(initialDelay * delayFactor.pow(attempt.toDouble()).toLong())
                }

                else -> {
                    purchasingSkuIdSet.remove(skuInfo.getSkuId())
                    return purchaseResult
                }
            }
        }
    }

    override suspend fun queryPurchases(): Rlt<PurchaseInfo> {
        return withContext(this.coroutineContext) {
            queryPurchasesRetry()
        }
    }

    private suspend fun queryPurchasesRetry(): Rlt<PurchaseInfo> {
        // 最大重试次数
        val maxRetries = 3
        // 初始延迟时间（毫秒）
        val initialDelay = 1000L
        // 延迟倍数，用于指数回退
        val delayFactor = 2.0
        var attempt = 0

        while (true) {
            val client = ensureConnection()
            if (client == null || client.isReady.not()) {
                return Rlt.Failed(SkuQueryError(PayRes.SET_UP_DISCONNECTED))
            }
            val result = client.queryPurchasesAsync(
                QueryPurchasesParams.newBuilder()
                    .setProductType(BillingClient.ProductType.INAPP)
                    .build()
            )
            val responseCode = result.billingResult.responseCode
            Log.i(TAG_GOOGLE_BILLING, "queryPurchases, resultCode: $responseCode")

            when (responseCode) {
                BillingClient.BillingResponseCode.OK -> {
                    val googlePurchaseInfo = PurchaseInfo(
                        PayChannel.Google,
                        result.purchasesList.map { GooglePurchase(it) }
                    )
                    return Rlt.Success(googlePurchaseInfo)
                }

                in setOf(
                    BillingClient.BillingResponseCode.ERROR,
                    BillingClient.BillingResponseCode.NETWORK_ERROR,
                    BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
                    BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
                ) -> {
                    attempt++
                    if (attempt >= maxRetries) {
                        return Rlt.Failed(
                            IError(serverCode = getPayResCodeFromBillingResponse(responseCode))
                        )
                    }
                    delay(initialDelay * delayFactor.pow(attempt.toDouble()).toLong())
                }

                else -> return Rlt.Failed(
                    IError(serverCode = getPayResCodeFromBillingResponse(responseCode))
                )
            }
        }
    }

    private suspend fun isFeatureSupported(feature: String): Boolean {
        return withContext(this.coroutineContext) {
            val client = ensureConnection()
            if (client == null || client.isReady.not()) {
                false
            } else {
                client.isFeatureSupported(feature).responseCode == BillingClient.BillingResponseCode.OK
            }
        }
    }

    private fun getPayResCodeFromBillingResponse(responseCode: Int): Int {
        return when (responseCode) {
            BillingClient.BillingResponseCode.OK -> PayRes.SUCCESS
            BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED -> PayRes.FEATURE_NOT_SUPPORTED
            BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> PayRes.SERVICE_DISCONNECTED
            BillingClient.BillingResponseCode.USER_CANCELED -> PayRes.PURCHASE_USER_CANCEL
            BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE -> PayRes.SERVICE_UNAVAILABLE
            BillingClient.BillingResponseCode.BILLING_UNAVAILABLE -> PayRes.BILLING_UNAVAILABLE
            BillingClient.BillingResponseCode.ITEM_UNAVAILABLE -> PayRes.ITEM_UNAVAILABLE
            BillingClient.BillingResponseCode.DEVELOPER_ERROR -> PayRes.DEVELOPER_ERROR
            BillingClient.BillingResponseCode.ERROR -> PayRes.ERROR
            BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> PayRes.ITEM_ALREADY_OWNED
            BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> PayRes.ITEM_NOT_OWNED
            BillingClient.BillingResponseCode.NETWORK_ERROR -> PayRes.NETWORK_ERROR
            else -> responseCode
        }
    }
}