package com.gh.gamecenter.feature.minigame.wechat

import android.annotation.SuppressLint
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.EnvHelper
import com.gh.gamecenter.common.utils.FixedSizeLinkedHashSet
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.WXMiniGameCPMResponse
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.retrofit.RetrofitManager
import com.lightgame.utils.Utils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers

object WGameSubjectCPMListReportHelper {

    private val clickSet by lazy { hashSetOf<WGSubjectReport>() }

    private val exposureSet by lazy { hashSetOf<Map<String, Any>>() }
    private val exposureRequestSet by lazy { hashSetOf<String>() }
    private val exposureCacheSet by lazy { FixedSizeLinkedHashSet<String>(100) }

    /**
     * recommendId为空，正在调用CPM接口的点击事件集合
     */
    private val pendingRecommendIdClickSet by lazy { hashSetOf<WGSubjectPendingClick>() }

    fun reportClick(
        gameEntity: GameEntity,
        operateTime: Long = System.currentTimeMillis() / 1000
    ) {
        val recommendId = gameEntity.miniGameRecommendId.ifEmpty {
            gameEntity.exposureEvent?.payload?.miniGameRecommendId // 如果游戏实体的recommendId为空，则取它绑定的曝光事件的recommendId
        } ?: return
        if (recommendId.isNotEmpty()) {
            reportClickInner(
                miniAppUid = gameEntity.miniGameUid,
                gameName = gameEntity.name ?: "",
                operateTime = System.currentTimeMillis() / 1000,
                recommendId = recommendId
            )
            return
        }
        // 没有曝光事件的点击事件上报是不合理的
        val exposureEvent = gameEntity.exposureEvent ?: return
        // 正在调用接口请求获取recommendId，需要等待recommendId回调以后再执行上报
        pendingRecommendIdClickSet.add(WGSubjectPendingClick(operateTime, exposureEvent))
        Utils.log(
            "WXGameCPMReport",
            "当前微信CPM小游戏正在请求CPM接口获取RecommendId，将延迟到RecommendId获取后上报：${gameEntity.name}, ${gameEntity.miniGameAppId}"
        )
    }

    private fun reportClickInner(
        miniAppUid: String,
        gameName: String,
        operateTime: Long,
        recommendId: String
    ) {
        AppExecutor.logExecutor.execute {
            clickSet.add(
                WGSubjectReport(
                    appId = miniAppUid,
                    gameName = gameName,
                    operateTime = operateTime,
                    recommendId = recommendId,
                    posCard = 0,
                    posInCard = 0
                )
            )
            if (exposureRequestSet.contains(recommendId)) {
                Utils.log(
                    "WXGameCPMReport",
                    "当前微信CPM小游戏点击事件对应的曝光事件处于提交状态，将延迟到曝光事件提交后上报：$gameName, $recommendId"
                )
            }
            commitReportClick()
        }

    }

    @SuppressLint("CheckResult")
    private fun commitReportClick() {
        AppExecutor.logExecutor.execute {
            val reportClickSet = clickSet
                .filterNot {
                    exposureRequestSet.contains(it.recommendId)
                }
            report(
                reportClickSet.map {
                    Utils.log(
                        "WXGameCPMReport",
                        "将微信CPM小游戏加入点击事件队列：${it.gameName}, ${it.recommendId}"
                    )
                    mapOf(
                        "appID" to it.appId,
                        "operateTime" to it.operateTime,
                        "recommendID" to it.recommendId,
                        "posCard" to it.posCard,
                        "posInCard" to it.posInCard
                    )
                },
                WGSubjectCPMReportType.CLICK
            )
            clickSet.removeAll(reportClickSet.toSet())
        }
    }

    fun reportExposure(exposure: ExposureEvent) {
        val recommendId = exposure.payload.miniGameRecommendId ?: return
        if (recommendId.isEmpty()) {// recommendId为空时，需要请求CPM接口获取recommendId的值
            requestRecommendIdAndReportExposure(exposure)
        } else {
            reportExposureInner(exposure)
        }
    }

    /**
     * 先请求CPM接口获取RecommendId，再提交曝光事件
     */
    @SuppressLint("CheckResult")
    private fun requestRecommendIdAndReportExposure(exposure: ExposureEvent) {
        val wxappid = exposure.payload.miniGameUid!!
        val meta = MetaUtil.getMeta()
        val request = mapOf(
            "head" to mapOf(
                "busiAppid" to EnvHelper.getWGameCPMBusiAppId(),
                "oaid" to (meta.oaid ?: ""),
                "manufacturer" to (meta.manufacturer ?: ""),
                "mode" to (meta.model ?: ""),
                "androidId" to (MetaUtil.getAndroidId()),
                "imei" to (MetaUtil.getIMEI())
            ),
            "body" to mapOf(
                "appIDList" to listOf(wxappid)
            )
        )
        Utils.log(
            "WXGameCPMReport",
            "请求CPM接口获取RecommendId，${exposure.payload.gameName}"
        )
        RetrofitManager.getInstance()
            .wGameCPMApiService
            .getGameInfoList(request.toRequestBody())
            .subscribe({
                if (it.ret == 0 && it.appInfoList.isNotEmpty()) {
                    exposure.payload.miniGameRecommendId = it.appInfoList[0].recommendID
                    Utils.log(
                        "WXGameCPMReport",
                        "请求CPM接口获取RecommendId成功！${exposure.payload.gameName}, ${exposure.payload.miniGameRecommendId}"
                    )
                    reportExposureInner(exposure)// 上报曝光事件
                    pendingRecommendIdClickSet.forEach { click ->
                        if (click.exposure === exposure) {// 上报延迟的点击事件
                            reportClickInner(
                                miniAppUid = exposure.payload.miniGameUid ?: "",
                                gameName = exposure.payload.gameName ?: "",
                                operateTime = click.operateTime,
                                recommendId = exposure.payload.miniGameRecommendId!!
                            )
                        }
                    }
                }
                pendingRecommendIdClickSet.removeAll {
                    click -> click.exposure === exposure }
            }, {
                pendingRecommendIdClickSet.removeAll {
                    click -> click.exposure === exposure }
            })
    }

    private fun reportExposureInner(exposure: ExposureEvent) {
        val recommendId = exposure.payload.miniGameRecommendId ?: return
        val gameName = exposure.payload.gameName ?: ""
        if (exposureCacheSet.contains(recommendId)) {
            Utils.log("WXGameCPMReport", "过滤重复曝光的微信CPM小游戏：$gameName, $recommendId")
            return
        }
        exposureSet.add(
            mapOf(
                "appID" to (exposure.payload.miniGameUid ?: ""),
                "operateTime" to exposure.timeInMillisecond / 1000,
                "recommendID" to recommendId,
                "posCard" to 0,
                "posInCard" to 0
            )
        )
        exposureRequestSet.add(recommendId)
        exposureCacheSet.add(recommendId)
        Utils.log("WXGameCPMReport", "将微信CPM小游戏加入曝光事件队列：$gameName, $recommendId")
        commitReportExposure()
    }

    fun reportExposure(exposureSet: Set<ExposureEvent>) {
        exposureSet.forEach { reportExposure(it) }
    }

    @SuppressLint("CheckResult")
    private fun commitReportExposure() {
        AppExecutor.logExecutor.execute {
            if (exposureSet.isEmpty()) {
                return@execute
            }
            report(
                exposureSet.toList(),
                WGSubjectCPMReportType.EXPOSURE
            )
            exposureSet.clear()
        }
    }

    @SuppressLint("CheckResult")
    private fun report(list: List<Map<String, Any>>, type: WGSubjectCPMReportType) {
        if (list.isEmpty()) return
        val meta = MetaUtil.getMeta()
        val request = mapOf(
            "head" to mapOf(
                "busiAppid" to EnvHelper.getWGameCPMBusiAppId(),
                "oaid" to (meta.oaid ?: ""),
                "manufacturer" to (meta.manufacturer ?: ""),
                "mode" to (meta.model ?: ""),
                "androidId" to (MetaUtil.getAndroidId()),
                "imei" to (MetaUtil.getIMEI())
            ),
            "body" to mapOf(
                "appList" to list
            )
        )
        val observable = if (type == WGSubjectCPMReportType.EXPOSURE) {
            RetrofitManager.getInstance()
                .wGameCPMApiService
                .reportExposure(request.toRequestBody())
                .doOnSuccess {
                    val requestExposureIdSet = list.map {
                        it["recommendID"].toString()
                    }.toSet()
                    exposureRequestSet.removeAll(requestExposureIdSet)
                    commitReportClick() // 上报所有延迟提交的点击事件
                }
        } else {
            RetrofitManager.getInstance()
                .wGameCPMApiService
                .reportClick(request.toRequestBody())
        }
        observable
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object : BiResponse<WXMiniGameCPMResponse>() {
                override fun onSuccess(data: WXMiniGameCPMResponse) {
                    if (data.ret == 0) {
                        val eventType = if (type == WGSubjectCPMReportType.CLICK) "点击" else "曝光"
                        Utils.log("WXGameCPMReport", "提交了${list.size}条${eventType}事件记录")
                    }
                }
            })
    }

    enum class WGSubjectCPMReportType { EXPOSURE, CLICK }

    class WGSubjectReport(
        val appId: String = "",
        val gameName: String = "",
        val operateTime: Long = 0,
        val recommendId: String = "",
        val posCard: Int = 0,
        val posInCard: Int = 0
    )

    class WGSubjectPendingClick(
        val operateTime: Long,
        val exposure: ExposureEvent
    )
}