package com.duolebo.blyrobot.data

import android.content.Context
import android.os.Build
import android.util.Log
import com.duolebo.appbase.AppBaseHandler
import com.duolebo.appbase.IAppBaseCallback
import com.duolebo.appbase.IProtocol
import com.duolebo.blyrobot.protocol.ApkReportProtocol
import com.duolebo.blyrobot.util.AdbUtil
import com.duolebo.blyrobot.util.AppUtil
import com.duolebo.blyrobot.util.Config
import net.gotev.uploadservice.*
import net.gotev.uploadservice.ftp.FTPUploadRequest
import net.gotev.uploadservice.ftp.UnixPermissions
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList


class Task : Thread, IAppBaseCallback {

    enum class State {
        IDLE, RUNNING, INTERRUPT, SCREEN_SHOT_FAILED, UPLOADING, REPORT_FAILED, COMPLETE, MANUAL_STOP
    }

    private val TAG = "RobotTask"

    lateinit var apkInfo: ApkInfo
    // 截图存放路径
    private lateinit var imagePath: String
    // 抓包存放路径
    private var capturePath = AppUtil.getAbsoluteSdcardPath() + "/capture.txt"

    private var uploadImages = ArrayList<String>()
    private var uploadId = ""

    private var channelIndex = 0
    var status: State = State.IDLE

    private var reportProtocol: ApkReportProtocol
    private var dataHandler: AppBaseHandler
    private lateinit var reportJson: JSONObject

    private var context: Context
    private val imageDateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.CHINA)
    var proc: Process? = null

    constructor(context: Context) {
        this.context = context
        this.reportProtocol = ApkReportProtocol(context, Config.instance)
        this.dataHandler = AppBaseHandler(this)
    }

    fun isRunning(): Boolean {
        if (status == State.COMPLETE)
            return false
        return true
    }

    fun from(apkInfo: ApkInfo) {
        this.apkInfo = apkInfo
//        this.imagePath = this.context.cacheDir.absolutePath + "/" + apkInfo.packageName
        val sdPath = AppUtil.getAbsoluteSdcardPath()
        this.imagePath = sdPath + "/upload/" + apkInfo.packageName
        val dir = File(this.imagePath)
        if (!dir.exists())
            dir.mkdirs()
    }

    // 准备上传报告信息
    private fun prepareReport() {
        this.reportJson = JSONObject()
        reportJson.putOpt(ApkInfo.NAME, this.apkInfo.name)
        reportJson.putOpt(ApkInfo.PACKAGE_NAME, this.apkInfo.packageName)
        reportJson.putOpt("device", Build.BRAND + "|" + Build.HARDWARE + "|" + Build.VERSION.RELEASE)
        reportJson.putOpt("channels", JSONArray())
    }

    override fun run() {
        super.run()
        execute()
    }

    private fun execute() {
        //如果之前的上传任务还在执行，进行取消
        if (!uploadId.isNullOrEmpty()) {
            UploadService.stopUpload(uploadId)
        }

        this.status = State.RUNNING
        this.prepareReport()
        // 启动应用
        this.launchApp(true)
        // 频道轮询处理
        try {
            var success = this.processChannels(0)
            // 再次尝试
            if (!success) {
                if (this.status == State.MANUAL_STOP) {
                    Log.i(TAG, "manual exit")
                    return
                }

                this.launchApp(true)
                // 重新切换到上次崩溃的频道,接着抓取
                for (i in 0 until this.channelIndex) {
                    AdbUtil.sendMultiKey(this.apkInfo.channelKeyEvent)
                }
                success = this.processChannels(this.channelIndex)

                if (!success && this.status == State.MANUAL_STOP)
                    return
            }

            // 上传图片
            this.uploadImage()
            this.status = State.UPLOADING
        } catch (e: java.lang.Exception) {
            this.status = State.INTERRUPT
            e.printStackTrace()
        }
        // 退出应用
        AdbUtil.stopApp(this.apkInfo.packageName)

        // 上报数据，上传图片在后台进行
        report()
    }

    fun reset() {
        this.status = State.IDLE
        this.channelIndex = 0
        this.uploadImages.clear()

        val dir = File(this.imagePath)
        if (dir.exists()) {
            for (file in dir.listFiles())
                file.delete()
        }
    }

    fun release() {
        // 杀掉tcpdump进程
        AdbUtil.killTcpdump()

        // 退出应用
        AdbUtil.stopApp(this.apkInfo.packageName)

        // 取消ftp上传
//        if (!uploadId.isNullOrEmpty())
//            UploadService.stopUpload(uploadId)
    }

    fun exit() {
        this.status = State.MANUAL_STOP
        release()
    }

    private fun finish(result: Boolean) {
        this.taskListener?.run {
            onComplete(result)
        }
    }

    private fun launchApp(reset: Boolean = false) {
        Log.i(TAG, "launchApp")
        if (reset) {
            try {
                AdbUtil.resetApp(this.apkInfo.packageName)
            } catch (e: java.lang.Exception) {
                e.printStackTrace()
            }
        }

        AdbUtil.launchApp("${this.apkInfo.packageName}/${this.apkInfo.launcher}")
        // 启动立即抓取，避免第一个频道没有抓到
        capture(this.apkInfo.captureDelay, false)
        Thread.sleep(this.apkInfo.launchDelay * 1000L)

        // 启动后按键事件模拟
        if (!this.apkInfo.launchKeyEvent.isNullOrEmpty()) {
            AdbUtil.sendMultiKey(this.apkInfo.launchKeyEvent!!)
        }
    }

    private fun processChannels(index: Int): Boolean {
        Log.i(TAG, "processChannels")
        step()
        quitCapture()
        for (i in index + 1 until this.apkInfo.channelCount) {

            if (this.status == State.MANUAL_STOP)
                return false
            // 模拟按键事件. 切换频道进行抓取
            this.channelIndex = i
            capture(apkInfo.captureDelay)
            AdbUtil.sendMultiKey(this.apkInfo.channelKeyEvent)

            if (!AppUtil.isAppForeground(context, this.apkInfo.packageName)) {
                Log.i(TAG, "targe app crashed?")
                return false
            }

            step()
        }

        return true
    }

    private fun step() {
        Log.i(TAG, "step channel ${this.channelIndex}")

        var playUrlItems = analysisCapture()
        // 如果没有抓取到地址，再次尝试
        if (playUrlItems.size <= 0) {
            capture(this.apkInfo.captureDelay)
            playUrlItems = analysisCapture()
        }
        // 截图保存
        val screenShots = saveScreenShot()
        if (screenShots.size > 0)
            uploadImages.addAll(screenShots)

        val channels = this.reportJson.optJSONArray("channels")
        val channelJson = JSONObject()

        val channelImageFile = File(screenShots[0])
        channelJson.putOpt("channelImage", channelImageFile.name)
        val channelEpgImageFile = File(screenShots[1])
        channelJson.putOpt("channelEpgImage", channelEpgImageFile.name)

        channelJson.putOpt("channelId", this.channelIndex)

        val playUrls = JSONArray()
        for (playUrl in playUrlItems) {
            playUrls.put(playUrl)
        }

        channelJson.putOpt("playUrls", playUrls)

        channels.put(channelJson)
    }

    // 抓包处理
    private fun capture(delay: Int, quit: Boolean = true) {
        Log.i(TAG, "capture")
        val file = File(this.capturePath)
        if (file.exists())
            file.delete()
        Thread(Runnable {
            proc = AdbUtil.tcpCapture(this.capturePath)
        }).start()

        if (quit) {
            Log.i(TAG, "capture sleep $delay seconds")
            Thread.sleep(delay * 1000L)
            quitCapture()
        }
    }

    private fun quitCapture() {
        proc?.destroy()
        AdbUtil.killTcpdump()
        Thread.sleep(2000L)
    }

    // 分析http抓包文件
    private fun analysisCapture(): ArrayList<String> {
        val playUrlItems = ArrayList<String>()
        val file = File(this.capturePath)
        if (!file.exists())
            return playUrlItems

        val lines = file.readLines()

        var partUrl = ""
        var timeStr = ""
        val tagGet = "GET /"
        lines.forEach {
            if (it.contains("IP (")) {
                val end = it.indexOf('.')
                timeStr = it.substring(0, end)
            }

            if (it.contains(tagGet)) {
                val start = it.indexOf(tagGet) + tagGet.length - 1
                val end = it.lastIndexOf(' ')
                partUrl = it.substring(start, end)
            }

            if (it.contains("Host")) {
                val start = it.indexOf(' ') + 1
                val host = it.substring(start)
                val url = "http://$host$partUrl"

                Log.i(TAG, "capture url: $url")

                var add = true
                // 如果筛选url不为空，这里需要进行过滤
                if (!this.apkInfo.filterUrl.isEmpty()) {
                    if (!url.contains(this.apkInfo.filterUrl))
                        add = false
                }

                if (add) {
                    // 这里进行媒体视频播放地址识别
                    add = false
                    val extArr = this.apkInfo.mediaExt.split("|")
                    for (ext in extArr) {
                        if (url.contains(ext)) {
                            add = true
                            break
                        }
                    }
                }

                if (add && !playUrlItems.contains(url)) {

                    playUrlItems.add(url)
                }

                partUrl = ""
            }
        }

        return playUrlItems
    }

    // 截取一张频道图+一张显示频道节目的图
    private fun saveScreenShot(): ArrayList<String> {
        val screenImages = ArrayList<String>()

        var time = imageDateFormat.format(Date())
        val absName = this.imagePath + "/${this.apkInfo.packageName}_${channelIndex}_$time"
        screenShot(absName)
        screenImages.add("$absName.jpg")

        AdbUtil.sendMultiKey(this.apkInfo.menuKeyEvent)
        time = imageDateFormat.format(Date())
        val absOkName = this.imagePath + "/${this.apkInfo.packageName}_${channelIndex}_${time}_ok"
        screenShot(absOkName)
        screenImages.add("$absOkName.jpg")

        return screenImages
    }

    // 截图处理，转成jpg
    private fun screenShot(absName: String) {
        val pngPath = "$absName.png"
        val jpgPath = pngPath.replace(".png", ".jpg")
        AdbUtil.screenShot(pngPath)
        // png转换成jpg
        val pngFile = File(pngPath)
        if (pngFile.exists())
            AppUtil.pngToJpg(pngPath, jpgPath)
        else {
            Log.i(TAG, "screen shot failed")
        }
    }

    private fun uploadComplete(errorCount: Int) {
        Log.i(TAG, "upload errorCount: $errorCount")
    }

    private fun uploadImageCallback(): UploadStatusDelegate {
        val totalUpload = this.uploadImages.size
        Log.i(TAG, "total upload count : $totalUpload")

        return object : UploadStatusDelegate {
            override fun onCancelled(context: Context?, uploadInfo: UploadInfo?) {
            }

            override fun onProgress(context: Context?, uploadInfo: UploadInfo?) {
            }

            override fun onError(context: Context?, uploadInfo: UploadInfo?, serverResponse: ServerResponse?, exception: java.lang.Exception?) {
                val uploadSize = uploadInfo?.successfullyUploadedFiles!!.size
                Log.i(TAG, "onError...uploadCount: $uploadSize")
            }

            override fun onCompleted(context: Context?, uploadInfo: UploadInfo?, serverResponse: ServerResponse?) {
                val uploadSize = uploadInfo?.successfullyUploadedFiles!!.size
                Log.i(TAG, "onCompleted...uploadCount: $uploadSize")
                uploadComplete(totalUpload - uploadSize)
            }
        }
    }

    private fun uploadImage() {

        try {
            val uploadRequest = FTPUploadRequest(context, Config.instance.getFtpServer(), 21)
                    .setUsernameAndPassword(Config.instance.getFtpUserName(), Config.instance.getFtpPassword())
                    .setNotificationConfig(UploadNotificationConfig())
                    .setCreatedDirectoriesPermissions(UnixPermissions("777"))
                    .setSocketTimeout(15000)
                    .setConnectTimeout(15000)
                    .setDelegate(uploadImageCallback())
                    .setMaxRetries(4)

            this.uploadImages.forEach {
                val uploadFile = File(it)
                if (uploadFile.exists()) {
                    uploadRequest.addFileToUpload(it, Config.instance.getFtpRemotePath() + uploadFile.name)
                }
            }

            this.uploadId = uploadRequest.startUpload()
            Log.i(TAG, "upload id $uploadId")
        } catch (exc: Exception) {
            Log.e(TAG, exc.message, exc)
        }
    }

    private fun report() {
        val postJson = JSONObject()
        val reportArray = JSONArray()
        reportArray.put(this.reportJson)
        postJson.putOpt("data", reportArray)
        reportProtocol.withBody(postJson.toString()).execute(dataHandler)
    }

    override fun onProtocolFailed(p0: IProtocol?) {
        this.status = State.REPORT_FAILED
        finish(false)
    }

    override fun onHttpFailed(p0: IProtocol?) {
        this.status = State.REPORT_FAILED
        finish(false)
    }

    override fun onProtocolSucceed(p0: IProtocol?) {
        this.status = State.COMPLETE
        finish(true)
    }

    var taskListener: OnTaskListener? = null

    interface OnTaskListener {
        fun onComplete(result: Boolean)
    }

}