package com.gh.download.server

import com.gh.download.DownloadManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.FileUtils
import com.gh.gamecenter.common.utils.getMetaExtra
import com.gh.gamecenter.common.utils.toObject
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.halo.assistant.HaloApp
import fi.iki.elonen.NanoHTTPD
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import java.io.File
import java.io.FileInputStream
import java.util.concurrent.TimeUnit

class DownloadServer(port: Int) : NanoHTTPD(port) {

    private val mPublishSubject: PublishSubject<String> = PublishSubject.create()
    private val mCompositeDisposable: CompositeDisposable = CompositeDisposable()

    // 是否是山寨机
    var isBuggyDevice: Boolean = false

    init {
        // 某些浏览器会开启多线程下载，如果不 throttle 的话会出现多次调用
        val disposable = mPublishSubject
            .distinctUntilChanged()
            .throttleWithTimeout(500, TimeUnit.MILLISECONDS)
            .subscribeOn(Schedulers.io())
            .subscribe({
                logBrowserDownload(it, false)
            }, {})

        mCompositeDisposable.add(disposable)
    }

    /**
     * 默认截掉 .apk 后缀的原因是部分系统判断到 url 后缀为 .apk 就会调用系统下载器而不是浏览器下载 apk (下载完成不会自动触发安装)
     */
    override fun serve(
        fileName: String?,
        method: Method?,
        headers: MutableMap<String, String>?,
        parms: MutableMap<String, String>?,
        files: MutableMap<String, String>?
    ): Response {
        var filePath = FileUtils.getDownloadDir(HaloApp.getInstance()) + fileName
        if (!filePath.contains(APK_SUFFIX)) {
            filePath += APK_SUFFIX
        }
        var file = File(filePath)
        // 找不到名字全匹配的文件时，进入文件夹里检索一遍
        if (!file.exists() && fileName != null && !fileName.contains("favicon")) {
            // 后台可能上传后缀有为 ".apk.apk" 的包！
            val fileDir = File(FileUtils.getDownloadDir(HaloApp.getInstance()))
            fileDir.listFiles { dir, name ->
                if (name.contains(fileName.removePrefix(File.separator))) {
                    file = File(dir.path + File.separator + name)
                    return@listFiles true
                }
                return@listFiles false
            }
        }

        mPublishSubject.onNext(filePath)

        return if (file.exists()) {
            val fileType = FileUtils.getFileMimeType(HaloApp.getInstance().applicationContext, filePath)
                ?: "application/vnd.android.package-archive"

            // TODO 山寨机给了文件长度会下载失败，有空再看看为什么
            if (isBuggyDevice) {
                newChunkedResponse(Response.Status.OK, fileType, DownloadFileInputStream(file)).apply {
                    addHeader("Content-Disposition", "inline; filename=\"$filePath")
                }
            } else {
                newFixedLengthResponse(Response.Status.OK, fileType, DownloadFileInputStream(file), file.length())
            }
        } else {
            newFixedLengthResponse("找不到文件")
        }
    }

    companion object {
        const val APK_SUFFIX = ".apk"

        fun logBrowserDownload(path: String, isDownloadComplete: Boolean) {
            for (download in DownloadManager.getInstance().allDownloadEntity) {
                if (download.path == path) {
                    download.exposureTrace?.toObject<ExposureEvent>()?.let {
                        BrowserInstallHelper.logWebDownloadStarted(
                            it,
                            download.getMetaExtra(Constants.DOWNLOAD_ID),
                            isDownloadComplete
                        )
                    }
                    break
                }
            }
        }
    }

}

class DownloadFileInputStream(val file: File?) : FileInputStream(file) {
    override fun read(b: ByteArray?, off: Int, len: Int): Int {
        // 不为 BUFFER_LENGTH 的时候算是下载完了
        if (len != 16 * 1024) {
            DownloadServer.logBrowserDownload(file?.path ?: "", true)
        }
        return super.read(b, off, len)
    }
}