@file:OptIn(ExperimentalSerializationApi::class)

package moe.fuqiuluo.qqinterface.servlet

import com.tencent.mobileqq.app.QQAppInterface
import com.tencent.mobileqq.transfile.HttpNetReq
import com.tencent.mobileqq.transfile.INetEngineListener
import com.tencent.mobileqq.transfile.NetReq
import com.tencent.mobileqq.transfile.NetResp
import com.tencent.mobileqq.transfile.ServerAddr
import com.tencent.mobileqq.transfile.api.IHttpEngineService
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.readBytes
import kotlinx.io.core.writeFully
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.encodeToByteArray

import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.utils.MD5
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import protobuf.fav.WeiyunAddRichMediaReq
import protobuf.fav.WeiyunAuthor
import protobuf.fav.WeiyunCollectCommInfo
import protobuf.fav.WeiyunComm
import protobuf.fav.WeiyunCommonReq
import protobuf.fav.WeiyunFastUploadResourceReq
import protobuf.fav.WeiyunGetFavContentReq
import protobuf.fav.WeiyunGetFavListReq
import protobuf.fav.WeiyunMsgHead
import protobuf.fav.WeiyunPicInfo
import protobuf.fav.WeiyunRichMediaContent
import protobuf.fav.WeiyunRichMediaSummary
import mqq.manager.TicketManager
import oicq.wlogin_sdk.request.Ticket
import oicq.wlogin_sdk.request.WtTicketPromise
import oicq.wlogin_sdk.tools.ErrMsg
import protobuf.auto.toByteArray
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.ByteBuffer
import kotlin.coroutines.resume


/**
 * QQ收藏相关接口
 */
internal object QFavSvc: BaseSvc() {
    private val SERVER_LIST_COLLECTOR = listOf(ServerAddr().also {
        it.isIpv6 = false
        it.mIp = "collector.weiyun.com"
        it.port = 80
    })
    private val SERVER_LIST_PICUP = listOf(ServerAddr().also {
        it.isIpv6 = false
        it.mIp = "pic.pieceup.qq.com"
        it.port = 80
    })
    private const val VERSION = 12820
    private const val APPID = 30244
    private const val SUB_APPID = 538116905
    private const val MAJOR_VERSION = 8
    private const val MINOR_VERSION = 9
    private var seq = 1

    suspend fun getItemList(
        category: Int,
        startPos: Int,
        pageSize: Int,
    ): Result<NetResp> {
        val baseReq = WeiyunCommonReq(
            getFavListReq = WeiyunGetFavListReq(
                type = 0u,
                bid = 0u,
                category = category.toUInt(),
                startTime = 0u,
                orderType = 0u,
                startPos = startPos.toUInt(),
                pageSize = pageSize.toUInt(),
                syncPolicy = 0u,
                reqSource = 0u
            )
        )
        return sendWeiyunReq(20000, baseReq)
    }

    suspend fun getItemContent(
        id: String
    ): Result<NetResp> {
        return sendWeiyunReq(20001, WeiyunCommonReq(
            getFavContentReq = WeiyunGetFavContentReq(
                cidList = arrayListOf(id)
            )
        )
        )
    }

    suspend fun addImageMsg(
        uin: Long,
        name: String,
        groupId: Long = 0,
        groupName: String = "",
        picUrl: String,
        pid: String,
        width: Int, height: Int,
        size: Long,
        md5: String,
    ): Result<NetResp> {
        val md5Bytes = md5.hex2ByteArray()
        return sendWeiyunReq(20009, WeiyunCommonReq(
            addRichMediaReq = WeiyunAddRichMediaReq(
                commInfo = WeiyunCollectCommInfo(
                    bid = 1u,
                    category = 1u,
                    author = WeiyunAuthor(
                        type = if (groupId == 0L) 1u else 2u,
                        numId = uin.toULong(),
                        strId = name,
                        groupId = groupId.toULong(),
                        groupName = groupName
                    ),
                    createTime = System.currentTimeMillis().toULong() - 2000u,
                    seq = System.currentTimeMillis().toULong() - 1000u,
                    bizDataList = arrayListOf("""{"recordAudioOnly":false,"audioOnly":false,"fileOnly":false}""".toByteArray()),
                    originalAppId = 0u,
                    customGroupId = 0u
                ),
                summary = WeiyunRichMediaSummary(
                    title = "",
                    brief = "[图片]",
                    picList = arrayListOf(
                        WeiyunPicInfo(
                            uri = picUrl,
                            md5 = md5Bytes,
                            sha1 = md5.toByteArray(),
                            name = "",
                            note = "",
                            width = width.toUInt(),
                            height = height.toUInt(),
                            size = size.toULong(),
                            type = 0u,
                            picId = pid
                        )
                    ),
                    contentType = 1u
                ),
                richMediaContent = listOf(
                    WeiyunRichMediaContent(
                    rawData = """<img src="$picUrl" />""".toByteArray(),
                    picList = listOf(
                        WeiyunPicInfo(
                        uri = picUrl,
                        md5 = md5Bytes,
                        sha1 = md5.toByteArray(),
                        name = "",
                        note = "",
                        width = width.toUInt(),
                        height = height.toUInt(),
                        size = size.toULong(),
                        type = 0u,
                        picId = pid
                    )
                    )
                )
                )
            )
        )
        )
    }

    suspend fun applyUpImageMsg(
        uin: Long,
        name: String,
        groupId: Long = 0,
        groupName: String = "",
        width: Int, height: Int,
        image: File
    ): Result<NetResp> {
        if (!image.exists()) {
            return Result.failure(IllegalArgumentException("image file not exists"))
        }
        val md5 = MD5.genFileMd5(image.absolutePath)
        return sendWeiyunReq(20010, WeiyunCommonReq(
            fastUploadResourceReq = WeiyunFastUploadResourceReq(
                picInfoList = listOf(
                    WeiyunPicInfo(
                    md5 = md5,
                    name = md5.toHexString(),
                    width = width.toUInt(),
                    height = height.toUInt(),
                    size = image.length().toULong(),
                    type = 1u,
                    picId = "/storage/emulated/0/DCIM/temp.jpeg",
                    owner = WeiyunAuthor(
                        type = if (groupId == 0L) 1u else 2u,
                        numId = uin.toULong(),
                        strId = name,
                        groupId = groupId.toULong(),
                        groupName = groupName
                    )
                )
                ),
            )
        )
        )
    }

    suspend fun addRichMediaMsg(
        uin: Long,
        name: String,
        groupId: Long = 0,
        groupName: String = "",
        time: Long = System.currentTimeMillis(),
        content: String
    ): Result<NetResp> {
        return sendWeiyunReq(20009, WeiyunCommonReq(
            addRichMediaReq = WeiyunAddRichMediaReq(
                commInfo = WeiyunCollectCommInfo(
                    bid = 1u,
                    category = 1u,
                    author = WeiyunAuthor(
                        type = if (groupId == 0L) 1u else 2u,
                        numId = uin.toULong(),
                        strId = name,
                        groupId = groupId.toULong(),
                        groupName = groupName
                    ),
                    createTime = time.toULong() - 2000u,
                    seq = time.toULong() - 1000u,
                    originalAppId = 0u,
                    customGroupId = 0u
                ),
                summary = WeiyunRichMediaSummary(
                    brief = content,
                    contentType = 1u
                ),
                richMediaContent = listOf(
                    WeiyunRichMediaContent(
                    rawData = content.textToHtml().toByteArray(),
                )
                )
            )
        )
        )
    }

    private fun String.textToHtml(): String {
        return replace("\n", "<div><br/></div>")
    }

    suspend fun sendPicUpBlock(
        fileSize: Long,
        offset: Long,
        block: ByteArray,
        blockSize: Long,
        sha: ByteArray,
        pid: String,
        outputStream: ByteArrayOutputStream = ByteArrayOutputStream(),
    ): Result<NetResp> {
        return suspendCancellableCoroutine {
            val httpNetReq = HttpNetReq()
            httpNetReq.userData = null
            httpNetReq.mCallback = object: INetEngineListener {
                override fun onResp(netResp: NetResp) {
                    if (netResp.mHttpCode != 200 && netResp.mResult != 0 && netResp.mErrDesc.isNullOrEmpty()) {
                        netResp.mErrDesc = netResp.mRespProperties["User-ErrMsg"]
                    }
                    netResp.mRespData = outputStream.toByteArray().copyOf()
                    it.resume(Result.success(netResp))
                }

                override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {}
            }
            val vi = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getA2(app.currentAccountUin)
            //LogCenter.log(pSKey)
            httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
            httpNetReq.mSendData = BytePacketBuilder().apply {
                writeInt(-1412589450)
                writeInt(10000)
                writeInt(0)
                writeInt(sha.size + 16 + blockSize.toInt())
                writeShort(0)
                writeShort(sha.size.toShort())
                writeFully(sha)
                writeInt(fileSize.toInt())
                writeInt(offset.toInt())
                writeInt(blockSize.toInt())
                writeFully(block)
            }.build().readBytes()
            httpNetReq.mOutStream = outputStream
            httpNetReq.mStartDownOffset = 0L
            httpNetReq.mReqProperties["Shamrock"] = "true"
            httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;pid=%s;appid=%d", app.currentAccountUin, 8, vi, pid, APPID)
            httpNetReq.mReqProperties["host"] = "pic.pieceup.qq.com"
            httpNetReq.mReqProperties["Range"] = "bytes=0-"
            httpNetReq.mReqProperties["Content-Length"] = httpNetReq.mSendData.size.toString()
            httpNetReq.mReqProperties["Accept-Encoding"] = "gzip"
            httpNetReq.mReqProperties["Content-Encoding"] = "gzip"
            httpNetReq.mPrioty = 1
            httpNetReq.mReqUrl = "https://pic.pieceup.qq.com/"
            httpNetReq.mServerList = SERVER_LIST_PICUP
            val service = AppRuntimeFetcher.appRuntime
                .getRuntimeService(IHttpEngineService::class.java, "qqfav")
            service.sendReq(httpNetReq)
        }
    }

    suspend fun sendWeiyunReq(
        cmd: Int,
        req: WeiyunCommonReq,
        outputStream: ByteArrayOutputStream = ByteArrayOutputStream(),
    ): Result<NetResp> {
        return suspendCancellableCoroutine {
            val httpNetReq = HttpNetReq()
            httpNetReq.userData = null
            httpNetReq.mCallback = object: INetEngineListener {
                override fun onResp(netResp: NetResp) {
                    if (netResp.mHttpCode != 200 && netResp.mResult != 0 && netResp.mErrDesc.isNullOrEmpty()) {
                        netResp.mErrDesc = netResp.mRespProperties["User-ErrMsg"]
                    }
                    netResp.mRespData = outputStream.toByteArray().copyOf()
                    it.resume(Result.success(netResp))
                }

                override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {}
            }
            val pSKey = getWeiYunPSKey()
            httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
            httpNetReq.mSendData = DeflateTools.gzip(packData(packHead(cmd, pSKey), WeiyunComm(
                req = req
            ).toByteArray()))
            httpNetReq.mOutStream = outputStream
            httpNetReq.mStartDownOffset = 0L
            httpNetReq.mReqProperties["Shamrock"] = "true"
            httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;appid=%d", app.currentAccountUin, 27, pSKey, APPID)
            httpNetReq.mReqProperties["host"] = "collector.weiyun.com"
            httpNetReq.mReqProperties["Range"] = "bytes=0-"
            httpNetReq.mReqProperties["Content-Length"] = httpNetReq.mSendData.size.toString()
            httpNetReq.mReqProperties["Accept-Encoding"] = "gzip"
            httpNetReq.mReqProperties["Content-Encoding"] = "gzip"
            httpNetReq.mPrioty = 1
            httpNetReq.mReqUrl = "https://collector.weiyun.com/collector.fcg"
            httpNetReq.mServerList = SERVER_LIST_COLLECTOR
            val service = AppRuntimeFetcher.appRuntime
                .getRuntimeService(IHttpEngineService::class.java, "qqfav")
            service.sendReq(httpNetReq)
        }
    }

    private fun packHead(cmd: Int, pskey: String): ByteArray {
        return WeiyunMsgHead(
            uin = app.longAccountUin.toULong(),
            seq = seq++.toUInt(),
            type = 1u,
            cmd = cmd.toUInt(),
            appId = APPID.toUInt(),
            version = VERSION.toUInt(),
            netType = 3u,
            keyType = 27u,
            key = pskey.toByteArray(),
            majorVersion = MAJOR_VERSION.toUInt(),
            minorVersion = MINOR_VERSION.toUInt(),
        ).toByteArray()
    }

    private fun packData(head: ByteArray, body: ByteArray): ByteArray {
        val len = 16 + head.size + body.size
        val buf = ByteBuffer.allocate(len)
        buf.putInt(SUB_APPID)
        buf.putShort(1)
        buf.putInt(len)
        buf.putInt(body.size)
        buf.putShort(0)
        buf.put(head)
        buf.put(body)
        return buf.array()
    }

    private fun getWeiYunPSKey(): String {
        val pskey = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager)
            .getPskey(app.currentAccountUin, 16L, arrayOf("weiyun.com"), WeiYunPSKeyPromise)
        return if (pskey != null) pskey.getPSkey("weiyun.com") else ""
    }

    private object WeiYunPSKeyPromise: WtTicketPromise {
        override fun Done(ticket: Ticket) {
            LogCenter.log("Fav: getPskeyPromise: done", Level.DEBUG)
        }

        override fun Failed(errMsg: ErrMsg) {
            LogCenter.log("Fav: getPskeyPromise: failed, $errMsg", Level.DEBUG)
        }

        override fun Timeout(errMsg: ErrMsg) {
            LogCenter.log("Fav: getPskeyPromise: timeout, $errMsg", Level.DEBUG)
        }
    }
}