package com.gh.ndownload

import android.net.Uri
import android.os.Build
import android.text.TextUtils
import androidx.annotation.WorkerThread
import com.gh.common.util.PackageInstaller
import com.gh.download.DownloadDataHelper
import com.gh.download.DownloadDataSimpleHelper
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.feature.utils.SentryHelper
import com.halo.assistant.HaloApp
import com.lg.download.DownloadError
import com.lg.download.listener.InnerDownloadListener
import com.lg.ndownload.*
import com.lightgame.download.DownloadDao
import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Utils
import com.tencent.vasdolly.reader.ChannelReader
import com.tencent.vasdolly.writer.ChannelWriter
import org.json.JSONException
import org.json.JSONObject
import java.io.File
import java.io.IOException
import java.net.MalformedURLException
import java.net.URL
import java.net.URLConnection
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.*
import java.util.concurrent.ConcurrentHashMap

/**
 * 是串联丑陋的下载模块与新下载模块之间的桥梁
 */
object NDownloadBridge : InnerDownloadListener, IErrorRetryHandler {

    private const val TAG = "NDownloadBridge"
    private const val REAL_FORMAT = "real_format" // 最终的文件格式
    private const val DEFAULT_DOWNLOAD_THREAD_SIZE = 2
    private const val NO_NETWORK_MAX_RETRY_COUNT = 5

    const val REDIRECTED_HOST_LIST = "redirected_host_list" // 重定向后的 url host 数组
    const val REDIRECTED_URL_LIST = "redirected_url_list" // 重定向后的 url 数组

    private var mRetryTimer: Timer? = null // 用于错误重试的 timer
    private val mRetryCountMap: ConcurrentHashMap<String, Int> by lazy { ConcurrentHashMap() }

    private val mStartUpTimeMap: ConcurrentHashMap<String, Long> by lazy { ConcurrentHashMap() }

    private val mProgressDf : DecimalFormat by lazy { DecimalFormat("#.#", DecimalFormatSymbols(Locale.CHINA)) }

    init {
        DownloadQueue.getInstance().setMaxDownloadingTask(3)
    }

    override fun onError(id: String, error: DownloadError?, exception: Exception?) {
        Utils.log(TAG, "id $id encounter $error -> ${exception?.message}")

        resetRetryCount(id)
        getDownloadEntityFromDB(id)?.let { downloadEntity ->
            when (error) {
                DownloadError.EMPTY_URL,
                DownloadError.HTTP_NOT_FOUND,
                DownloadError.CONTENT_LENGTH_IS_ZERO -> {
                    doCancel(downloadEntity)
                    downloadEntity.status = com.lightgame.download.DownloadStatus.notfound
                }
                DownloadError.HIJACKED -> {
                    doCancel(downloadEntity)
                    downloadEntity.status = com.lightgame.download.DownloadStatus.hijack
                }
                DownloadError.DOWNLOAD_SIZE_NOT_MATCH_CONTENT_LENGTH -> {
                    doCancel(downloadEntity)
                    downloadEntity.status = com.lightgame.download.DownloadStatus.overflow
                    DownloadManager.getInstance().onTaskError(downloadEntity)
                }
                DownloadError.PREVIOUS_DOWNLOAD_IS_DELETED,
                DownloadError.FILE_CORRUPTED -> {
                    doCancel(downloadEntity)
                    ToastUtils.toast("文件已损坏，请重新下载")
                    downloadEntity.status = com.lightgame.download.DownloadStatus.cancel
                }
                DownloadError.PRE_DOWNLOAD_INTERNAL_ERROR,
                DownloadError.DOWNLOAD_CONNECTION_ERROR,
                DownloadError.PRE_DOWNLOAD_CONNECTION_ERROR -> {
                    if (exception is DownloadConnectionErrorException) {
                        downloadEntity.status = com.lightgame.download.DownloadStatus.resourceUnavailable
                        downloadEntity.addMetaExtra(Constants.DOWNLOAD_PAUSE_EXTRA, "${exception.message}")
                        if (downloadEntity.progress == 0L) {
                            doCancel(downloadEntity)
                        } else {
                            DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
                        }

                        ToastUtils.toast("游戏下载异常，请稍后再试 ${exception.message}")
                    } else {
                        downloadEntity.status = com.lightgame.download.DownloadStatus.neterror
                        DownloadManager.getInstance().onTaskError(downloadEntity)
                        NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity.url)
                    }

                    DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
                }
                DownloadError.DISK_IO_ERROR -> {
                    downloadEntity.status = com.lightgame.download.DownloadStatus.diskioerror
                    DownloadManager.getInstance().onTaskError(downloadEntity)
                    NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity.url)

                    DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
                }
                DownloadError.DISK_IS_FULL -> {
                    downloadEntity.status = com.lightgame.download.DownloadStatus.diskisfull
                    NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity.url)

                    DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
                }
                DownloadError.RESOURCE_IS_FORBIDDEN -> {
                    val errorString = exception?.message
                    if (!errorString.isNullOrEmpty() && exception !is DownloadResourceForbiddenException) {
                        handlePreDownloadForbiddenException(downloadEntity, errorString)

                        doCancel(downloadEntity)
                    } else {
                        downloadEntity.addMetaExtra(Constants.DOWNLOAD_PAUSE_EXTRA, "403")
                        downloadEntity.status = com.lightgame.download.DownloadStatus.resourceUnavailable

                        if (downloadEntity.progress == 0L) {
                            doCancel(downloadEntity)
                        } else {
                            DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
                        }

                        ToastUtils.toast("游戏下载太火爆了，请稍后再试")
                    }
                }
                DownloadError.PERSONAL_NO_CERTIFICATION -> {
                    doCancel(downloadEntity)
                    downloadEntity.status = com.lightgame.download.DownloadStatus.uncertificated
                }
                DownloadError.PERSONAL_TEENAGER -> {
                    doCancel(downloadEntity)
                    downloadEntity.status = com.lightgame.download.DownloadStatus.unqualified
                }
                else -> {
                    // do nothing
                }
            }
            NDataChanger.notifyDataChanged(downloadEntity)
        }
    }

    override fun onProgress(id: String?, progress: Float) {
        getDownloadEntityFromSnapshot(id)?.let { downloadEntity ->
            var percent = (progress * 100.0)
            percent = mProgressDf.format(percent).toDouble()

            downloadEntity.progress = (downloadEntity.size * progress).toLong()
            downloadEntity.percent = percent

            // 有可能还处于 redirected 状态，这里检查并置换状态
            if (com.lightgame.download.DownloadStatus.downloading != downloadEntity.status) {
                downloadEntity.status = com.lightgame.download.DownloadStatus.downloading
            }

            DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)

            NDataChanger.notifyDataChanged(downloadEntity)
        }
    }

    override fun onProgressWithoutThrottle(id: String?, progress: Float) {
        if (id != null && (mStartUpTimeMap[id] ?: 0) > 0) {
            val startupTime: Long = System.currentTimeMillis() - (mStartUpTimeMap[id] ?: 0)
            DownloadDataSimpleHelper.updateDownloadDataSimpleEntity(id, downloadStartUpTime = startupTime)
            mStartUpTimeMap[id] = 0
        }
    }

    override fun onSizeReceived(id: String?, fileSize: Long) {
        Utils.log(TAG, "id $id onSizeReceived $fileSize")

        getDownloadEntityFromDB(id)?.let { downloadEntity ->
            downloadEntity.size = fileSize

            DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
        }
    }

    override fun onReadyToDownload(id: String?, actualThreadSize: Int) {
        Utils.log(TAG, "id $id onReadyToDownload, actualThreadSize is -> $actualThreadSize")

        getDownloadEntityFromDB(id)?.let { downloadEntity ->
            downloadEntity.meta[DownloadDataHelper.DOWNLOAD_THREAD_SIZE] = actualThreadSize.toString()

            DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
        }
    }

    override fun onStatusChanged(id: String, status: com.lg.download.DownloadStatus?) {
        Utils.log(TAG, "id $id onStatusChanged $status")

        resetRetryCount(id)
        getDownloadEntityFromDB(id)?.let { downloadEntity ->
            when (status) {
                com.lg.download.DownloadStatus.CANCELLED -> {
                    doCancel(downloadEntity)

                    DownloadManager.getInstance().onTaskPaused(downloadEntity)
                    NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity.url)
                }
                com.lg.download.DownloadStatus.PAUSED -> {
                    downloadEntity.speed = 0
                    downloadEntity.status = com.lightgame.download.DownloadStatus.pause

                    NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity.url)
                }
                com.lg.download.DownloadStatus.WAITINGWIFI, com.lg.download.DownloadStatus.QUEUED -> {
                    downloadEntity.status = com.lightgame.download.DownloadStatus.waiting
                }
                com.lg.download.DownloadStatus.DOWNLOADING -> {
                    downloadEntity.status = com.lightgame.download.DownloadStatus.downloading
                    DownloadManager.getInstance().onTaskAdded(downloadEntity)
                }
                else -> {
                    Utils.log(TAG, "status not supported yet -> $status")
                }
            }

            DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)

            NDataChanger.notifyDataChanged(downloadEntity)
        }
    }

    override fun onDownloadComplete(id: String?, elapsedTime: Long) {
        getDownloadEntityFromDB(id)?.let { originDownloadEntity ->
            val downloadEntity = originDownloadEntity.deepClone<DownloadEntity>() ?: return@let

            downloadEntity.speed = 0
            downloadEntity.progress = downloadEntity.size
            downloadEntity.percent = 100.0
            downloadEntity.end = System.currentTimeMillis()
            downloadEntity.status = com.lightgame.download.DownloadStatus.done
            downloadEntity.addMetaExtra(com.lightgame.download.DownloadConfig.KEY_DOWNLOAD_ELAPSED_TIME, elapsedTime.toString())

            val originalFormat = downloadEntity.format
            val realFormat = downloadEntity.getMetaExtra(REAL_FORMAT)
            // 若最终的格式有异常，那么根据最后获得的格式进行修改、重命名下载文件
            if (realFormat.isNotEmpty() && originalFormat != realFormat) {
                Utils.log(TAG, "更新最终的文件格式，更新前的格式为 $originalFormat 更新后的格式为 $realFormat")

                val renamedId = PackageInstaller.createDownloadId(downloadEntity.name)
                val renamedPath = PackageInstaller.getDownloadPathWithId(
                    renamedId, downloadEntity.getMetaExtra(REAL_FORMAT)
                )
                File(downloadEntity.path).renameTo(File(renamedPath))
                downloadEntity.path = renamedPath
                downloadEntity.format = realFormat
            }

            // 为下载好的 mod 游戏添加 OAID，格式为 "o|oaid"
            if (downloadEntity.getMetaExtra(Constants.EXTRA_IS_MODDED_GAME) == "true") {
                val file = File(downloadEntity.path)
                val oaidString = "o|${HaloApp.getInstance().oaid}"
                try {
                    if (ChannelReader.containV2Signature(file)) {
                        ChannelWriter.addChannelByV2(file, oaidString, true)
                    } else if (ChannelReader.containV1Signature(file) && ChannelReader.getChannelByV1(file).isEmpty()) {
                        ChannelWriter.addChannelByV1(file, oaidString)
                    }
                } catch (e: Exception) {
                    SentryHelper.onEvent(
                        "ADD_CHANNEL_FAILED",
                        "game_id", downloadEntity.gameId,
                        "error_digest", e.localizedMessage
                    )
                }
            }


            // 适配 Android 6.0 的权限问题，尝试将文件设置为外部也可读
            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
                try {
                    File(downloadEntity.path).setReadable(true, false)
                } catch (e: SecurityException) {
                    e.printStackTrace()
                }
            }

            DownloadManager.getInstance().onTaskDone(downloadEntity)
            NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity.url)

            DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)

            NDataChanger.notifyDataChanged(downloadEntity)
        }
    }

    override fun onSpeedChanged(id: String, speed: Float) {
        getDownloadEntityFromSnapshot(id)?.let { downloadEntity ->
            downloadEntity.speed = speed.toLong()

            NDataChanger.notifyDataChanged(downloadEntity)
        }
    }

    override fun onRedirectingUrl(id: String, connection: URLConnection?, config: DownloadConfig?) {
        getDownloadEntityFromDB(id)?.let { downloadEntity ->
            if (!TextUtils.isEmpty(downloadEntity.eTag)) {
                val eTag: String = getRealETag(connection?.getHeaderField("Halo-ETag") ?: "")
                downloadEntity.haloEtag = eTag
            }

            // 记录重定向时获取到的最终格式，因为缓存问题，原格式与实际下载的格式有区别
            try {
                val realFormat = connection?.getHeaderField("Halo-Format")
                if (!realFormat.isNullOrEmpty() && downloadEntity.format != realFormat) {
                    Utils.log(TAG, "获取到新的文件格式, $realFormat")

                    downloadEntity.addMetaExtra(REAL_FORMAT, realFormat)
                }

                // 记录重定向过程中所有的 host
                val redirectedHostArray = downloadEntity.getMetaExtra(REDIRECTED_HOST_LIST).toObject<ArrayList<String>>() ?: arrayListOf()
                val redirectedUrlArray = downloadEntity.getMetaExtra(REDIRECTED_URL_LIST).toObject<ArrayList<String>>() ?: arrayListOf()
                connection?.getHeaderField("Location")?.let {
                    val url = URL(it)
                    redirectedHostArray.add(url.host)
                    redirectedUrlArray.add(url.toString())
                }
                downloadEntity.addMetaExtra(REDIRECTED_HOST_LIST, redirectedHostArray.toJson())
                downloadEntity.addMetaExtra(REDIRECTED_URL_LIST, redirectedUrlArray.toJson())

                DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
            } catch (e: NullPointerException) {
                // getHeaderField 的时候内部可能会触发 NullPointerException，原因未知
                // 由于这里的异常不会影响正常下载，所以直接打印异常，不做处理
                // 具体可见 https://sentry.shanqu.cc/organizations/lightgame/issues/371082/
                e.printStackTrace()
            } catch (e: MalformedURLException) {
                // 由于重定向的 url 可能是一个不合法的 url，这里捕获异常
                e.printStackTrace()
            }

            NDataChanger.notifyDataChanged(downloadEntity)
        }
    }

    override fun onRedirectedUrl(id: String, connection: URLConnection?, redirectedUrl: String?) {
        getDownloadEntityFromDB(id)?.let { downloadEntity ->
            downloadEntity.finalRedirectedUrl = redirectedUrl
            val url = URL(redirectedUrl)
            if (url.host != null && url.path != null) {
                downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] = url.host
                downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] = url.path
                DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
            }

            downloadEntity.status = com.lightgame.download.DownloadStatus.redirected

            // downloadEntity 的 etag 的值用来判断是否需要进行劫持校验，值不参与到最后判断是否被劫持的校验中去
            val shouldCompareETag = !TextUtils.isEmpty(downloadEntity.eTag)
            val currentETag = connection?.getHeaderField("ETag")
            if (shouldCompareETag
                && !TextUtils.isEmpty(currentETag)
                && currentETag != downloadEntity.haloEtag
            ) {
                // 链接被劫持，抛出异常
                downloadEntity.status = com.lightgame.download.DownloadStatus.hijack
            }

            NDataChanger.notifyDataChanged(downloadEntity)
        }
    }

    override fun getRetryOption(id: String, e: Exception?): Int {
        val downloadEntity =
            DownloadDao.getInstance(HaloApp.getInstance()).getSnapshot(id) ?: return IErrorRetryHandler.RETRY_DIRECTLY

        return if (e is DownloadResourceForbiddenException
            || e is DownloadConnectionErrorException
            || (e is IOException && FileUtils.getFreeSpaceByPath(downloadEntity.path) < 10F)
        ) {
            // 资源不可用(403) 或者 下载异常 (400~500) IO异常(空间不够)时，放弃重试下载
            IErrorRetryHandler.REFUSE_TO_RETRY
        } else if (NetworkUtils.isWifiConnected(HaloApp.getInstance()) || isDownloadViaTrafficAllowed(downloadEntity)) {
            IErrorRetryHandler.RETRY_DIRECTLY
        } else {
            IErrorRetryHandler.RETRY_MANUALLY
        }
    }

    override fun retryManually(id: String, retryRunnable: Runnable, pauseRunnable: Runnable) {
        if (mRetryTimer == null) {
            mRetryTimer = Timer()
        }

        Utils.log(TAG, "about to retry download manually")

        mRetryTimer?.schedule(object : TimerTask() {
            override fun run() {
                if (NetworkUtils.isWifiConnected(HaloApp.getInstance())) {
                    resetRetryCount(id)
                    retryRunnable.run()
                } else {
                    mRetryCountMap[id] = (mRetryCountMap[id] ?: 0) + 1
                    if (mRetryCountMap[id]!! <= NO_NETWORK_MAX_RETRY_COUNT) {
                        retryManually(id, retryRunnable, pauseRunnable)
                    } else {
                        resetRetryCount(id)
                        pauseRunnable.run()
                    }
                }
            }
        }, GlobalDownloadConfig.ERROR_RETRY_INTERVAL)
    }

    fun isDownloadViaTrafficAllowed(downloadEntity: DownloadEntity?): Boolean {
        val mNetworkMobileStatus = "2G3G4G5G"
        val networkStatus: String? = downloadEntity?.meta?.get(DownloadEntity.NETWORK_STATUS_KEY)
        Utils.log("download network status$networkStatus")
        return if (networkStatus == null) false else mNetworkMobileStatus.contains(networkStatus)
    }

    fun download(downloadEntity: DownloadEntity) {
        val originUrl = downloadEntity.url
        // 双下载为畅玩时添加额外 query 规避实名
        val modifiedUrl = if (downloadEntity.isVGameDownloadInDualDownloadMode()) {
            Uri.parse(originUrl).buildUpon().appendQueryParameter("smooth", "true").build().toString()
        } else {
            originUrl
        }

        val config = DownloadConfigBuilder()
            .setUniqueId(originUrl)
            .setFileName("")
            .setUrl(modifiedUrl)
            .setPathToStore(downloadEntity.path)
            .setHttpClient(NHttpClient())
            .setDownloadThreadSize(DEFAULT_DOWNLOAD_THREAD_SIZE)
            .setDownloadListener(NDownloadBridge)
            .setErrorRetryHandler(this)
            .setDownloadExecutor(AppExecutor.downloadExecutor)
            .setMeta(downloadEntity.meta as HashMap<String, String>?)
            .setErrorRetryHandler(this)
            .build()

        mStartUpTimeMap[downloadEntity.url] = System.currentTimeMillis()

        DownloadQueue.getInstance().submitNewTask(config)
    }

    fun resume(id: String?) {
        id?.let {
            DownloadQueue.getInstance().resume(id)

            getDownloadEntityFromSnapshot(id)?.let { downloadEntity ->
                mStartUpTimeMap[downloadEntity.url] = System.currentTimeMillis()
            }
        }
    }

    fun pause(id: String?) {
        id?.let {
            DownloadQueue.getInstance().pause(id)
        }
    }

    fun cancel(id: String?) {
        id?.let {
            NDataChanger.downloadingTaskUrlSet.remove(id)
            DownloadQueue.getInstance().cancel(id)
        }
    }

    private fun doCancel(downloadEntity: DownloadEntity) {
        DownloadManager.getInstance().cancel(downloadEntity.url)
    }

    private fun getRealETag(originETag: String): String {
        var realETag = originETag
        if (!TextUtils.isEmpty(realETag) && realETag.startsWith("\"") && realETag.endsWith("\"")) {
            realETag = realETag.substring(1, realETag.length - 1)
        }
        return realETag
    }

    private fun handlePreDownloadForbiddenException(downloadEntity: DownloadEntity, errorString: String) {
        try {
            val resultObject = JSONObject(errorString)
            val code = resultObject.getString("code")

            if ("403001" == code) {
                // 未实名
                if (resultObject.has("force") && !resultObject.getBoolean("force")) {
                    downloadEntity.meta["force_real_name"] = "false"
                }
                downloadEntity.status = com.lightgame.download.DownloadStatus.uncertificated
            } else if ("403002" == code) {
                // 未成年
                if (resultObject.has("force") && !resultObject.getBoolean("force")) {
                    downloadEntity.meta["force_real_name"] = "false"
                }
                downloadEntity.status = com.lightgame.download.DownloadStatus.unqualified
            } else if ("403003" == code) {
                // 该游戏未接入防沉迷系统禁止下载
                downloadEntity.status = com.lightgame.download.DownloadStatus.unavailable
            } else if ("403004" == code) {
                // 后台禁止该设备下载
                downloadEntity.status = com.lightgame.download.DownloadStatus.banned
            } else if ("403005" == code) {
                // 实名中禁止下载
                downloadEntity.status = com.lightgame.download.DownloadStatus.isCertificating
            }
        } catch (e: JSONException) {
            e.printStackTrace()
        }
    }

    private fun resetRetryCount(id: String) {
        mRetryCountMap[id] = 0
    }

    /**
     * 获取下载实体快照
     */
    private fun getDownloadEntityFromSnapshot(id: String?): DownloadEntity? {
        return DownloadManager.getInstance().getDownloadEntitySnapshot(id)
    }

    /**
     * 从数据库中读出新的下载实体
     */
    @WorkerThread
    private fun getDownloadEntityFromDB(id: String?): DownloadEntity? {
        return DownloadManager.getInstance().getDownloadEntityByUrl(id)
    }

}