package com.gh.common.exposure

import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.loghub.TLogHubHelper
import com.gh.gamecenter.common.utils.FixedSizeLinkedHashSet
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.dsp.DspReportHelper
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.minigame.wechat.WGameSubjectCPMListReportHelper
import com.google.gson.ExclusionStrategy
import com.google.gson.FieldAttributes
import com.google.gson.GsonBuilder
import com.lightgame.utils.Utils
import com.volcengine.model.tls.LogItem

/**
 * A handful tool for committing logs to aliyun loghub.
 *
 * 如何简单地统计列表中每个 item 的曝光事件？
 *
 * 1. Adapter 实现 IExposable 接口，在 BindView 阶段更新 ExposureEvent，ExposureEvent 供 getEventByPosition(pos) 方法获取用
 * 2. 构建一个 ExposureListener 并作为入参添加至 recyclerview 的 onScroll 回调中
 * 3. 没了
 */
object ExposureManager {

    private const val STORE_SIZE = 100
    private const val LOG_STORE = BuildConfig.EXPOSURE_REPO

    // exposureCache 用来过滤掉具有相同 id 的曝光事件，避免重复发送事件
    private val exposureSet by lazy { hashSetOf<ExposureEvent>() }
    private val exposureCache by lazy { FixedSizeLinkedHashSet<String>(300) }

    private val gson by lazy {
        GsonBuilder()
            .addSerializationExclusionStrategy(object: ExclusionStrategy {
                override fun shouldSkipClass(clazz: Class<*>): Boolean {
                    return false
                }

                override fun shouldSkipField(f: FieldAttributes): Boolean {
                    return f.name == "additional"
                }
            })
            .create()
    }

    /**
     * Log a single exposure event.
     */
    fun log(event: ExposureEvent) {
        AppExecutor.logExecutor.execute {
            if (event.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM) {
                WGameSubjectCPMListReportHelper.reportExposure(event)
            } else if (event.payload.miniGameType == Constants.DSP_GAME) {
                if (event.event == ExposureType.DOWNLOAD_COMPLETE) {
                    DspReportHelper.report(event.payload.downloadUrl)
                }
            }
            if (!exposureCache.contains(event.id)) {
                exposureSet.add(event)
                exposureCache.add(event.id)
            } else {
                Utils.log("Exposure", "遇到重复曝光事件，自动过滤 （${event.id} - ${event.payload.gameName}）")
            }
        }
    }

    /**
     * Log a collection of exposure event.
     */
    fun log(eventList: List<ExposureEvent>) {
        AppExecutor.logExecutor.execute {
            for (event in eventList) {
                if (event != null && !exposureCache.contains(event.id)) {
                    exposureSet.add(event)
                    exposureCache.add(event.id)
                } else {
                    Utils.log("Exposure", "遇到重复曝光事件，自动过滤 （${event.id} - ${event.payload.gameName}）")
                }
            }
            commitSavedExposureEvents()
        }
    }

    /**
     * Log a collection of exposure event for external use.
     */
    fun logExternal(cpmEventList: List<ExposureEvent>, dspEventList: ArrayList<ExposureEvent>) {
        AppExecutor.logExecutor.execute {
            if (cpmEventList.isNotEmpty()) {
                WGameSubjectCPMListReportHelper.reportExposure(cpmEventList.toSet())
            }

            if (dspEventList.isNotEmpty()) {
                for (event in dspEventList) {
                    DspReportHelper.report(event.payload.showUrl)
                }
            }
        }
    }

    /**
     * Log a wechat mini game cpm collection of exposure event.
     */
    fun logCPM(event: ExposureEvent) {
        AppExecutor.logExecutor.execute {
            WGameSubjectCPMListReportHelper.reportExposure(event)
        }
    }

    /**
     * @param forcedUpload Ignore all restrictions.
     */
    fun commitSavedExposureEvents(forcedUpload: Boolean = false) {
        AppExecutor.logExecutor.execute {
            if (exposureSet.size < STORE_SIZE && !forcedUpload || exposureSet.size == 0) return@execute

            uploadExposures(exposureSet, forcedUpload)

            Utils.log("Exposure", "提交了${exposureSet.size}条曝光记录")
            exposureSet.clear()
        }
    }

    private fun eliminateMultipleBrackets(jsonWithMultipleBracket: String): String {
        return jsonWithMultipleBracket.replace("[[", "[").replace("]]", "]")
    }

    private fun uploadExposures(eventSet: HashSet<ExposureEvent>, forced: Boolean) {
        eventSet.forEach {
            TLogHubHelper.sendLog(buildLog(it), LOG_STORE)
//            LoghubHelper.uploadLog(buildLog(it), LOG_STORE, forced)
//            it.recycle()
        }
    }

    private fun buildLog(event: ExposureEvent) = LogItem(System.currentTimeMillis()).apply {
        addContent("__id", event.id)
        addContent("payload", gson.toJson(event.payload))
        addContent("event", event.event.toString())
        addContent("source", eliminateMultipleBrackets(gson.toJson(event.source)))
        addContent("meta", gson.toJson(event.meta))
        addContent("real_millisecond", event.timeInMillisecond.toString())
        addContent(
            "e-traces", if (event.eTrace != null) {
                eliminateMultipleBrackets(gson.toJson(event.eTrace))
            } else ""
        )
    }

}