package moe.protocol.servlet.helper

import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.msg.api.IMsgService
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import moe.protocol.servlet.msg.MessageMaker
import moe.fuqiuluo.xposed.helper.Level
import moe.fuqiuluo.xposed.helper.LogCenter
import moe.fuqiuluo.xposed.tools.EmptyJsonObject
import moe.fuqiuluo.xposed.tools.asJsonObject
import moe.fuqiuluo.xposed.tools.asJsonObjectOrNull
import moe.fuqiuluo.xposed.tools.asString
import moe.fuqiuluo.xposed.tools.json
import moe.fuqiuluo.xposed.tools.jsonArray
import moe.protocol.servlet.helper.db.MessageDB
import moe.protocol.servlet.helper.db.MessageMapping
import kotlin.math.abs

internal object MessageHelper {
    suspend fun sendMessageWithoutMsgId(chatType: Int, peerId: String, message: JsonArray, callback: IOperateCallback): Pair<Long, Int> {
        val service = QRoute.api(IMsgService::class.java)
        var uniseq = generateMsgId(chatType)
        var nonMsg: Boolean
        val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also {
            if (it.isEmpty()) error("消息合成失败，请查看日志或者检查输入。")
        }.filter {
            it.elementType != -1
        }.also {
            nonMsg = it.isEmpty()
        }
        if (!nonMsg) {
            service.sendMsg(
                generateContact(chatType, peerId),
                uniseq.second,
                msg as ArrayList<MsgElement>,
                callback
            )
        } else {
            uniseq = 0 to 0
        }
        return System.currentTimeMillis() to uniseq.first
    }

    suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
        val peerId = if (MsgConstant.KCHATTYPEC2C == chatType) {
            ContactHelper.getUidByUinAsync(id.toLong())
        } else id
        return Contact(chatType, peerId, subId)
    }

    fun obtainMessageTypeByDetailType(detailType: String): Int {
        return when(detailType) {
            "troop", "group" -> MsgConstant.KCHATTYPEGROUP
            "private" -> MsgConstant.KCHATTYPEC2C
            "less" -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
            "guild" -> MsgConstant.KCHATTYPEGUILD
            else -> error("不支持的消息来源类型")
        }
    }

    fun obtainDetailTypeByMsgType(msgType: Int): String {
        return when(msgType) {
            MsgConstant.KCHATTYPEGROUP -> "group"
            MsgConstant.KCHATTYPEC2C -> "private"
            MsgConstant.KCHATTYPEGUILD -> "guild"
            MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN -> "less"
            else -> error("不支持的消息来源类型")
        }
    }

    suspend fun messageArrayToMessageElements(chatType: Int, msgId: Long, targetUin: String, messageList: JsonArray): ArrayList<MsgElement> {
        val msgList = arrayListOf<MsgElement>()
        messageList.forEach {
            val msg = it.jsonObject
            try {
                val maker = MessageMaker[msg["type"].asString]
                if (maker != null) {
                    val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
                    maker(chatType, msgId, targetUin, data).onSuccess { msgElem ->
                        msgList.add(msgElem)
                    }.onFailure {
                        if (it.javaClass != ActionMsgException::class.java) {
                            throw it
                        }
                    }
                }
            } catch (e: Throwable) {
                LogCenter.log(e.stackTraceToString(), Level.ERROR)
            }
        }
        return msgList
    }

    fun generateMsgIdHash(chatType: Int, msgId: Long): Int {
        val key =  when (chatType) {
            MsgConstant.KCHATTYPEGROUP -> "grp$msgId"
            MsgConstant.KCHATTYPEC2C -> "c2c$msgId"
            else -> error("不支持的消息来源类型 | generateMsgIdHash: $chatType")
        }
        return abs(key.hashCode())
    }

    fun generateMsgId(chatType: Int): Pair<Int, Long> {
        val msgId = createMessageUniseq(chatType, System.currentTimeMillis())
        val hashCode: Int = convertMsgIdToMsgHash(chatType, msgId)
        return hashCode to msgId
    }

    fun getMsgMappingByHash(hash: Int): MessageMapping? {
        val db = MessageDB.getInstance()
        return db.messageMappingDao().queryByMsgHashId(hash)
    }

    fun getMsgMappingBySeq(chatType: Int, msgSeq: Int): MessageMapping? {
        val db = MessageDB.getInstance()
        return db.messageMappingDao().queryByMsgSeq(chatType, msgSeq)
    }

    inline fun convertMsgIdToMsgHash(chatType: Int, msgId: Long): Int {
        return generateMsgIdHash(chatType, msgId)
    }

    fun removeMsgByHashCode(hashCode: Int) {
        MessageDB.getInstance()
            .messageMappingDao()
            .deleteByMsgHash(hashCode)
    }

    fun saveMsgMapping(
        hash: Int,
        qqMsgId: Long,
        time: Long,
        chatType: Int,
        peerId: String,
        msgSeq: Int,
        subChatType: Int = chatType
    ) {
        val database = MessageDB.getInstance()
        val mapping = MessageMapping(hash, qqMsgId, chatType, subChatType, peerId, time, msgSeq)
        database.messageMappingDao().insert(mapping)
    }

    external fun createMessageUniseq(chatType: Int, time: Long): Long

    fun decodeCQCode(code: String): JsonArray {
        val arrayList = ArrayList<JsonElement>()
        val msgList = nativeDecodeCQCode(code)
        msgList.forEach {
            val params = hashMapOf<String, JsonElement>()
            it.forEach { (key, value) ->
                if (key != "_type") {
                    params[key] = value.json
                }
            }
            val data = hashMapOf(
                "type" to it["_type"]!!.json,
                "data" to JsonObject(params)
            )
            arrayList.add(JsonObject(data))
        }
        return arrayList.jsonArray
    }

    fun encodeCQCode(msg: ArrayList<HashMap<String, JsonElement>>): String {
        return nativeEncodeCQCode(msg.map {
            val params = hashMapOf<String, String>()
            it.forEach { (key, value) ->
                if (key != "type") {
                    value.asJsonObject.forEach { param, element ->
                        params[param] = element.asString
                    }
                } else {
                    params["_type"] = value.asString
                }
            }
            params
        })
    }

    private external fun nativeDecodeCQCode(code: String): List<Map<String, String>>
    private external fun nativeEncodeCQCode(segment: List<Map<String, String>>): String
}