package com.gh.ndownload

import android.text.TextUtils
import com.gh.download.DownloadDataHelper
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.utils.NetworkUtils
import com.gh.gamecenter.common.utils.addMetaExtra
import com.gh.gamecenter.common.utils.deepClone
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.ToastUtils
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.download.FileUtils
import com.lightgame.utils.Utils
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.math.RoundingMode
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 DEFAULT_DOWNLOAD_THREAD_SIZE = 2
    private const val NO_NETWORK_MAX_RETRY_COUNT = 5

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

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

    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)
        getDownloadEntity(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 -> {
                    downloadEntity.status = com.lightgame.download.DownloadStatus.neterror
                    DownloadManager.getInstance().onTaskError(downloadEntity)
                    NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity, true)

                    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, true)

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

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

                    doCancel(downloadEntity)
                }
                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) {
        getDownloadEntity(id)?.let { downloadEntity ->
            var percent = (progress * 100.0)
            val df = DecimalFormat("#.#", DecimalFormatSymbols(Locale.CHINA))
            df.roundingMode = RoundingMode.FLOOR
            percent = df.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)
            getDownloadEntity(id)?.let { downloadEntity ->
                downloadEntity.meta.put(DownloadEntity.DOWNLOAD_STARTUP_TIME_KEY, startupTime.toString())
            }
            mStartUpTimeMap[id] = 0
        }
    }

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

        getDownloadEntity(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")

        getDownloadEntity(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)
        getDownloadEntity(id)?.let { originDownloadEntity ->
            // 状态更新使用全新的拷贝对象，避免因为不同线程对数据的变更造成通知错乱
            val downloadEntity = originDownloadEntity.deepClone<DownloadEntity>() ?: return@let
            when (status) {
                com.lg.download.DownloadStatus.CANCELLED -> {
                    doCancel(downloadEntity)

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

                    NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity, false)
                }
                com.lg.download.DownloadStatus.WAITINGWIFI -> {
                    downloadEntity.status = com.lightgame.download.DownloadStatus.waiting
                }
                com.lg.download.DownloadStatus.DOWNLOADING -> {
                    downloadEntity.status = com.lightgame.download.DownloadStatus.downloading
                    DownloadManager.getInstance().onTaskAdded(downloadEntity)
                }
                com.lg.download.DownloadStatus.QUEUED -> {
                    downloadEntity.status = com.lightgame.download.DownloadStatus.add
                }
                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) {
        getDownloadEntity(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())

            DownloadManager.getInstance().onTaskDone(downloadEntity)
            NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity, true)

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

            NDataChanger.notifyDataChanged(downloadEntity)
        }
    }

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

            NDataChanger.notifyDataChanged(downloadEntity)
        }
    }

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

            NDataChanger.notifyDataChanged(downloadEntity)
        }
    }

    override fun onRedirectedUrl(id: String, connection: URLConnection?, redirectedUrl: String?) {
        getDownloadEntity(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 IOException && FileUtils.getFreeSpaceByPath(downloadEntity.path) < 10F) {
            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)
    }

    private 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 config = DownloadConfigBuilder()
            .setUniqueId(downloadEntity.url)
            .setFileName("")
            .setUrl(downloadEntity.url)
            .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)

            getDownloadEntity(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.downloadEntries.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 handleForbiddenException(downloadEntity: DownloadEntity, errorString: String) {
        try {
            val resultObject = JSONObject(errorString)

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

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

    private fun getDownloadEntity(id: String?): DownloadEntity? {
        return DownloadManager.getInstance().getDownloadEntitySnapshot(id)
    }

}