package com.gamedog.vididin.manager

import com.ama.core.architecture.util.AndroidUtil
import com.ama.core.architecture.util.DateUtil
import com.ama.core.architecture.util.DeviceUtil
import com.ama.core.architecture.util.MD5Util
import com.ama.core.architecture.util.NetUtil
import com.ama.core.architecture.util.SpUtil
import com.ama.core.architecture.util.eventbus.NotifyMan
import com.gamedog.vididin.VidiConst
import com.gamedog.vididin.VididinEvents
import com.gamedog.vididin.beans.RECORD_CASH_MINUS_WITHDRAW_SUCCESS
import com.gamedog.vididin.beans.RECORD_CASH_PLUS_WITHDRAW_FAIL
import com.gamedog.vididin.beans.RECORD_CASH_PLUS_WITHDRAW_ONGOING
import com.gamedog.vididin.beans.RecordCash
import com.gamedog.vididin.beans.req.PayInitReq
import com.gamedog.vididin.beans.req.PayoutCheckReq
import com.gamedog.vididin.beans.resp.WithdrawRecord
import com.gamedog.vididin.core.login.login.AccountManager
import com.gamedog.vididin.manager.WithdrawManager.Companion.STATE_NEED_WATCH_AD
import com.gamedog.vididin.netbase.NetworkUtil
import com.gamedog.vididin.netbase.Result
import com.viddin.videos.free.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.locks.ReentrantLock


class WithdrawManager private constructor() {

    private val backgroundScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    private val mRecordLocker = ReentrantLock()
    private val mRecordList: MutableList<WithdrawRecord> by lazy {
        SpUtil.instance().getList<WithdrawRecord>(SpUtil.KEY_WITHDRAW_HISTORY_LIST).toMutableList()
    }

    private val mItemList: MutableList<WithdrawItemBean> by lazy {
        val itemList = SpUtil.instance().getList<WithdrawItemBean>(SpUtil.KEY_WITHDRAW_ITEM_LIST).toMutableList()
        if (itemList.isEmpty()) {
            itemList.addAll(generateItemList())
            saveInfos2Sp(itemList)
        }
        itemList
    }

    companion object {
        const val EACH_SUB_ITEM_CASH_NUM: Double = 1.0


        // subBean 状态
        const val STATE_NEED_WATCH_AD: Int = 0
        const val STATE_COULD_WITHDRAW: Int = 1
        const val STATE_WITHDRAWING: Int = 2
        const val STATE_HAS_WITHDRAWED: Int = 3

        //  提现交易状态  提现状态 0: 未启动 1:提现中,2:提现成功,3:提现失败
        const val TRANSACTION_STATE_UNSTART : Int = 0
        const val TRANSACTION_STATE_ONGOING : Int = 1
        const val TRANSACTION_STATE_SUCCESS : Int = 2
        const val TRANSACTION_STATE_FAIL : Int = 3


        // 提现失败分类(自定义)
        const val FAIL_TYPE_UNKNOWN : Int = 1
        const val FAIL_TYPE_APP_VERSION_LOW : Int = 2


        @Volatile
        private var instance: WithdrawManager? = null
        fun instance(): WithdrawManager {
            return instance ?: synchronized(this) {
                instance ?: WithdrawManager().also {
                    instance = it
                }
            }
        }
    }

    init {
        loopCheckTransactionState()
    }



    private fun generateItemList(): MutableList<WithdrawItemBean> {
        val itemList = mutableListOf<WithdrawItemBean>()
        itemList.add(WithdrawItemBean(0, 0.1, isBigWithDraw = false))
        itemList.add(WithdrawItemBean(1, 10.0, AndroidUtil.randomInt(50, 70), generateSubItemList(10F)))
        itemList.add(WithdrawItemBean(2, 20.0, AndroidUtil.randomInt(50, 70), generateSubItemList(20F)))
        itemList.add(WithdrawItemBean(3, 50.0, AndroidUtil.randomInt(50, 70), generateSubItemList(50F)))
        itemList.add(WithdrawItemBean(4, 100.0, AndroidUtil.randomInt(50, 70), generateSubItemList(100F)))
        itemList.add(WithdrawItemBean(5, 300.0, AndroidUtil.randomInt(50, 70), generateSubItemList(300F)))
        return itemList
    }

    private fun generateSubItemList(totalCashInItem: Float): List<WithdrawSubItem> {
        val subItemList = mutableListOf<WithdrawSubItem>()
        val subItemCount: Int = (totalCashInItem/EACH_SUB_ITEM_CASH_NUM).toInt()

        for (i in 0..subItemCount-1) {
            val initProgress = AndroidUtil.randomInt(50, 70)
            subItemList.add(WithdrawSubItem(i, EACH_SUB_ITEM_CASH_NUM, initProgress, initProgress, 0.0))
        }
        return subItemList
    }

    private fun saveInfos2Sp(itemList: MutableList<WithdrawItemBean>) {
        SpUtil.instance().putList(SpUtil.KEY_WITHDRAW_ITEM_LIST, itemList)
    }

    fun getItemList(): List<WithdrawItemBean> {
        return mItemList
    }

    fun getItem(itemIndex: Int): WithdrawItemBean {
        return mItemList[itemIndex]
    }


    fun getHasWithdrawSuccessCashCount(): Double {
        var count = 0.0
        for (record in mRecordList) {
            if (record.state == TRANSACTION_STATE_SUCCESS) {
                count += record.cashNum
            }
        }

        // extra withdraw count from zeroBuy
        count += ZeroManager.instance().getZeroHasWithdrawSuccessCashCount()

        return count
    }

    fun getHasWithdrawSuccessCashCount(itemIndex: Int): Double {
        var count = 0.0
        for (record in mRecordList) {
            if (record.state == TRANSACTION_STATE_SUCCESS && record.itemIndex == itemIndex) {
                count += record.cashNum
            }
        }
        return count
    }

    fun addAdEarnForSubBean(itemIndex: Int, selectedSubIndex: Int, earnMoneyNum: Float) : Boolean {
        if (itemIndex >= 0 && itemIndex < mItemList.size) {
            try {
                val subBean = mItemList[itemIndex].subItemList[selectedSubIndex]
                subBean.hasEarnMoneyByAd += earnMoneyNum * 5 // dollar 2 bariz
                calculateSubBeanProgress(subBean)  //传入 itembean 更新 selectedIndex为下一个
                saveInfos2Sp(mItemList)
                notifyProgressUpdated(subBean)
                return true
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }

        return false
    }

    private fun notifyProgressUpdated(subBean: WithdrawSubItem) {
        NotifyMan.instance().sendEvent(VididinEvents.EVENT_WITHDRAW_SUB_ITEM_PROGRESS_UPDATED,
            NotifyMan.NotifyData(subBean.index))
    }

    private fun notifySelectedSubBeanChanged(subBean: WithdrawSubItem) {
        NotifyMan.instance().sendEvent(VididinEvents.EVENT_WITHDRAW_SELECTED_SUB_ITEM_CHANGED, NotifyMan.NotifyData(subBean.index))
    }

    private fun notifyItemListChanged() {
        NotifyMan.instance().sendEvent(VididinEvents.EVENT_WITHDRAW_ITEM_LIST_CHANGED, null)
    }


    private fun calculateSubBeanProgress(subBean: WithdrawSubItem) {
        val needEarnProgress = 100 - subBean.startAdProgress
        if (subBean.hasEarnMoneyByAd >= subBean.cashTotal) {
            subBean.currentAdProgress = 100
            // update state
            if (subBean.withdrawState == STATE_NEED_WATCH_AD) {
                subBean.withdrawState = STATE_COULD_WITHDRAW
            }
        } else {
            val newProgress = subBean.startAdProgress + (needEarnProgress * (subBean.hasEarnMoneyByAd / subBean.cashTotal)).toInt()
            subBean.currentAdProgress = if (newProgress >= 100) 100 else newProgress
        }
    }

    fun updateSubBeanState(subBean: WithdrawSubItem, newState: Int) {
        subBean.withdrawState = newState
        saveInfos2Sp(mItemList)
    }

    fun startItem(curItem: WithdrawItemBean) {
        if (curItem.startMs <= 0L) {
            curItem.startMs = DateUtil.getCurTimeMs() - (if (curItem.totalCashNum == 50.0) 24 * 3600000 * 4 else 0)
            saveInfos2Sp(mItemList)
        }
    }

    fun saveRecordHasNotifyState(recordNo: String) {
        val recordBean = getRecord(recordNo)
        recordBean?.let {
            it.hasShowResultDialog = true
            saveRecords2Sp()
        }
    }

    fun saveNewRecord(newRecord: WithdrawRecord) {
        try {
            mRecordLocker.lock()
            mRecordList.add(newRecord)
        } finally {
            mRecordLocker.unlock()
        }
        saveRecords2Sp()
        val cashNum = newRecord.cashNum * -1
        AccountManager.adjustCash(cashNum, RecordCash(
            RECORD_CASH_PLUS_WITHDRAW_ONGOING, cashNum.toDouble(), true).apply { uuid = newRecord.recordNo }
        )
        loopCheckTransactionState()
    }


    private fun saveRecords2Sp() {
        SpUtil.instance().putList(SpUtil.KEY_WITHDRAW_HISTORY_LIST,getClonedRecordList())
    }

    fun updateRecord(recordNo: String, newState: Int, failType: Int = 0) {
        var needSaveSp = false
        try {
            mRecordLocker.lock()
            mRecordList.forEachIndexed { index, record ->
                if (record.recordNo == recordNo) {
                    if (record.state != newState) {
                        needSaveSp =true
                        record.state = newState
                        record.failReason = failType

                        if (newState == STATE_HAS_WITHDRAWED) {
                            checkIfItemFinishAndReset(record.itemIndex)
                        }
                    }
                    return@forEachIndexed
                }
            }
        } finally {
            mRecordLocker.unlock()
        }

        if (needSaveSp) {
            saveRecords2Sp()
        }
    }

    private fun checkIfItemFinishAndReset(itemIndex: Int) {
        var needReset = false
        if (itemIndex == 0) {
            needReset = true
        } else {
            var allSubItemFinish = true
            mItemList[itemIndex].subItemList.forEach {
                if (it.withdrawState != STATE_HAS_WITHDRAWED) {
                    allSubItemFinish = false
                    return@forEach
                }
            }

            needReset = allSubItemFinish
        }

        if (needReset) {
            resetItem(itemIndex)
        }
    }

    private fun resetItem(itemIndex: Int) {
        val needResetItem = mItemList[itemIndex]
        needResetItem.apply {
            totalProgress = 0
            startMs = 0L
            hasStarted = false

            subItemList.forEach { subItem ->
                subItem.apply {
                    currentAdProgress = startAdProgress
                    hasEarnMoneyByAd = 0.0
                    withdrawState = STATE_NEED_WATCH_AD
                }
            }
        }

        notifyItemListChanged()
    }


    fun getClonedRecordList(): List<WithdrawRecord> {
        try {
            mRecordLocker.lock()
            val clonedList = mutableListOf<WithdrawRecord>()
            clonedList.addAll(mRecordList)
            return clonedList
        } finally {
            mRecordLocker.unlock()
        }
        return emptyList()
    }

    private fun getRecord(recordNo: String): WithdrawRecord? {
        try {
            mRecordLocker.lock()
            mRecordList.forEachIndexed { index, record ->
                if (record.recordNo == recordNo) {
                    return record
                }
            }
        } finally {
            mRecordLocker.unlock()
        }
        return null
    }


    private fun loopCheckTransactionState() {
        var unCheckCount = 0
        try {
            mRecordLocker.lock()
            mRecordList.forEach { record ->
                if (record.state == TRANSACTION_STATE_ONGOING) {
                    unCheckCount++
                }
            }
        } finally {
            mRecordLocker.unlock()
        }


        if (unCheckCount > 0) {
            try {
                mRecordLocker.lock()
                mRecordList.forEachIndexed { index, record ->
                    if (record.state == TRANSACTION_STATE_ONGOING) {
                        doTransactionCheck(record.recordNo, record.cashNum)
                    }
                }
            } finally {
                mRecordLocker.unlock()
            }
        }
    }

    private fun doTransactionCheck(recordNo: String, cashNum: Double) {
        backgroundScope.launch {
            performNetworkRequest(recordNo)
        }
    }

    private suspend fun performNetworkRequest(recordNo: String) {
        val requestParam = applyInitFields(PayoutCheckReq()).apply {
            record_no = recordNo
        }

        val reqResult = NetworkUtil.callApi {
            NetworkUtil.apiservice().withdrawCheck(requestParam)
        }

        when (reqResult) {
            is Result.Loading -> {

            }

            is Result.Success -> {
                val checkResult = reqResult.data.data
                var failedType = 0

                when (checkResult?.error) {
                    0 -> {
                        when (checkResult.status) {
                            //  提现状态 1:提现中,2:提现成功,3:提现失败
                            1 -> {
                                delay(10000)
                                loopCheckTransactionState()
                            }
                            2 -> {
                                handleTransactionSuccess(recordNo)
                            }
                            3 -> {
                                failedType = FAIL_TYPE_UNKNOWN
                            }
                        }
                    }

                    1 -> {
                        failedType = FAIL_TYPE_UNKNOWN
                    }

                    2 -> {
                        failedType = FAIL_TYPE_APP_VERSION_LOW
                    }
                }

                if (failedType > 0) {
                    handleTransactionFailed(recordNo, failedType)
                }
            }

            is Result.Error -> {

            }
        }
    }

    private fun handleTransactionSuccess(recordNo: String) {
        updateRecord(recordNo, TRANSACTION_STATE_SUCCESS)
        notifyWithdrawCheckResult(recordNo)
        val recordBean = getRecord(recordNo)
        recordBean?.let {
            RecordsManager.instance().updateCashRecord(RecordCash(
                RECORD_CASH_MINUS_WITHDRAW_SUCCESS, it.cashNum.toDouble(), true).apply { uuid = recordNo })

            updateFirstWithdraw01Task(it)
        }
    }

    private fun updateFirstWithdraw01Task(recordBean: WithdrawRecord) {
        if (recordBean.cashNum == 0.1 && !TaskManager.instance().newbieFirstWithdrawStatus().getStatusBean().hasClaimReward) {
            TaskManager.instance().newbieFirstWithdrawStatus().claimReward()
        }
    }

    private fun handleTransactionFailed(recordNo: String, failedType: Int) {
        updateRecord(recordNo, TRANSACTION_STATE_FAIL, failedType)
        val recordBean = WithdrawManager.instance?.getRecord(recordNo)
        recordBean?.let {
            AccountManager.adjustCash(it.cashNum,
                RecordCash(RECORD_CASH_PLUS_WITHDRAW_FAIL, it.cashNum.toDouble(), false).apply { uuid = recordNo })
        }
        notifyWithdrawCheckResult(recordNo)
    }

    private fun notifyWithdrawCheckResult(recordNo: String) {
        val recordBean = getRecord(recordNo)
        recordBean.let {
            NotifyMan.instance().sendEvent(VididinEvents.EVENT_WITHDRAW_CHECK_RESULT_UPDATED, NotifyMan.NotifyData(it))
        }
    }

    fun <T : PayInitReq> applyInitFields(dataBean: T): T {
        dataBean.apply {
            platform = "Android"
            deviceid = DeviceUtil.generateDeviceId()
            version = AndroidUtil.getAppVersionInfo()
            ip = NetUtil.getLocalIpAddress()
            ts = (System.currentTimeMillis()/1000).toString()
            val signOrigin = "${VidiConst.WITHDRAW_MD5KEY}platform=${platform}deviceid=${deviceid}version=${version}ip=${ip}ts=$ts"
            sign = MD5Util.md5ForWithDraw(signOrigin)
        }
        return dataBean
    }

    fun getFailShowTextRes(failReason: Int): Int {
        var failTextRes = R.string.withdraw_normal_fail
        when (failReason) {
            FAIL_TYPE_APP_VERSION_LOW -> {
                failTextRes = R.string.withdraw_fail_version_toolow
            }
        }
        return  failTextRes
    }

    private fun getStartedItemRestCashNum(itemIndex: Int): Double {
        if (itemIndex in 0..mItemList.size-1) {
            val curItem = mItemList[itemIndex]
            if (curItem.hasStarted) {
                var hasWithdrawedCash = 0.0
                curItem.subItemList.forEach { subItem->
                    if (subItem.withdrawState == STATE_HAS_WITHDRAWED) {
                        hasWithdrawedCash += subItem.cashTotal
                    }
                }
                return curItem.totalCashNum - hasWithdrawedCash
            }
        }
        return 0.0
    }

    private fun getStartedItemRestCashCount(): Double {
        var allStartedItemRestCashNum = 0.0
        mItemList.forEachIndexed { index, item ->
            if (!(TaskManager.instance().newbieFirstWithdrawStatus().getStatusBean().hasClaimReward && index == 0)) {
                allStartedItemRestCashNum += getStartedItemRestCashNum(index)
            }
        }

        return allStartedItemRestCashNum
    }


    fun getItemProgress(itemIndex: Int): Double {
        var itemProgress = 0.0

        if (itemIndex in 0..mItemList.size-1) {
            val curItem = mItemList[itemIndex]
            val userCashTotal = AccountManager.getCash()
            val restAvailableCashNum = userCashTotal - getStartedItemRestCashCount()

            if (curItem.hasStarted || restAvailableCashNum >= curItem.totalCashNum) {
                itemProgress = 1.0
            } else {
                itemProgress = restAvailableCashNum / curItem.totalCashNum
            }
        }

        return itemProgress
    }

    fun setItemStarted(itemIndex: Int) {
        if (itemIndex in 0..mItemList.size-1) {
            val curItem = mItemList[itemIndex]
            if (!curItem.hasStarted) {
                curItem.hasStarted = true
                curItem.startMs = System.currentTimeMillis()

                saveInfos2Sp(mItemList)
                notifyItemListChanged()
            }
        }
    }


}


data class WithdrawItemBean(
    val index: Int,
    val totalCashNum: Double,
    var totalProgress: Int = 0,
    val subItemList: List<WithdrawSubItem> = emptyList(),
    var startMs: Long = 0L,
    var hasStarted: Boolean = false,
    var isBigWithDraw: Boolean = true
)

data class WithdrawSubItem(
    val index: Int,
    val cashTotal: Double,
    val startAdProgress: Int = 0,
    var currentAdProgress: Int = 0,
    var hasEarnMoneyByAd: Double = 0.0,
    var withdrawState: Int = STATE_NEED_WATCH_AD,
)