package com.duolebo.blyrobot.data

import android.content.Context
import android.os.Build
import android.util.Log
import com.android.volley.DefaultRetryPolicy
import com.android.volley.DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
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.tools.FtpManager
import com.duolebo.blyrobot.util.AdbUtil
import com.duolebo.blyrobot.util.AppUtil
import com.duolebo.blyrobot.util.Config
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 taskPath: String
    // 抓包存放路径
    private var capturePath = AppUtil.getAbsoluteSdcardPath() + "/capture.txt"

    var uploadImages = ArrayList<String>()
    var uploadTask: ImageUploadTask ?= null

    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.reportProtocol.retryPolicy = DefaultRetryPolicy(30*1000, 3, DEFAULT_BACKOFF_MULT)
        this.dataHandler = AppBaseHandler(this)
    }

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

    /**
     * 从apk配置生成信息
     */
    fun from(apkInfo: ApkInfo) {
        this.apkInfo = apkInfo
        val sdPath = AppUtil.getAbsoluteSdcardPath()
        this.taskPath = sdPath + "/upload/" + apkInfo.packageName
        val dir = File(this.taskPath)
        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() {

        this.status = State.RUNNING
        this.prepareReport()
        // 启动应用
        this.launchApp(true)
        // 频道轮询处理
        try {
            var success = this.processChannels(0)
            // 再次尝试
            if (!success) {
                Log.i(TAG, "app seems to be quit,so try again")
                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) {
                    Log.i(TAG, "interrupt on channel: $channelIndex")
                    if (this.status == State.MANUAL_STOP) {
                        Log.i(TAG, "manual exit and interrupt")
                        return
                    }
                }
            }

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

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

    /**
     * 重置状态
     */
    fun reset() {
        this.status = State.IDLE
        this.channelIndex = 0
        this.uploadImages.clear()
    }

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

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

        // 取消ftp上传
        this.uploadTask?.run {
            FtpManager.instance.cancelTask(this)
        }

    }

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

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

    /**
     * 启动app
     */
    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)
        sleepBySeconds(this.apkInfo.launchDelay)

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

        sleepBySeconds(this.apkInfo.captureDelay)
    }

    /**
     * 频道轮询
     * @param index 频道序号
     */
    private fun processChannels(index: Int): Boolean {
        Log.i(TAG, "processChannels...count:${this.apkInfo.channelCount}")
        quitCapture()
        processChannelItem()

        for (i in index + 1 until this.apkInfo.channelCount) {

            if (this.status == State.MANUAL_STOP)
                return false

            val halfSeconds = apkInfo.captureDelay/2;
            this.channelIndex = i

            AdbUtil.sendMultiKey(this.apkInfo.channelKeyEvent)

            // 抓包挺
            capture(apkInfo.captureDelay, false)
            sleepBySeconds(halfSeconds + 1)

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

            processChannelItem()
            sleepBySeconds(halfSeconds)
            quitCapture()
        }

        return true
    }

    /**
     * 单个频道处理
     * 分析抓包数据+屏幕截图
     */
    private fun processChannelItem() {
        Log.i(TAG, "processChannelItem channel ${this.channelIndex}")

        try {
            // 处理抓包数据
            var playUrlItems = analysisCapture()
            // 如果没有抓取到地址，再次尝试
            if (playUrlItems.size <= 0) {
                capture(this.apkInfo.captureDelay)
                playUrlItems = analysisCapture()
            }
            val channels = this.reportJson.optJSONArray("channels")
            val channelJson = JSONObject()

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

            channelJson.putOpt("playUrls", playUrls)

            // 处理图片上传信息
            val screenImages = ArrayList<String>()

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

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

            // 对于一些epg弹出时间过长的应用，模拟点击一次返回键，比如电视家3.0
            if (this.apkInfo.epgBack)
                AdbUtil.sendMultiKey("back")

            if (screenImages.size > 0)
                uploadImages.addAll(screenImages)

            val channelImageFile = File(screenImages[0])
            channelJson.putOpt("channelImage", channelImageFile.name)
            val channelEpgImageFile = File(screenImages[1])
            channelJson.putOpt("channelEpgImage", channelEpgImageFile.name)
            channelJson.putOpt("channelId", this.channelIndex)

            channels.put(channelJson)
        } catch (e : Exception) {
            e.printStackTrace()
            Log.i(TAG, "exception processChannelItem channel ${this.channelIndex}")
        }
    }

    // 抓包处理
    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")
            sleepBySeconds(delay)
            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 {

            try {
                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(' ')
                    if (start < end && end < it.length)
                        partUrl = it.substring(start, end)
                    else
                        partUrl = "/"
                }

                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 = ""
                }
            } catch (e: Exception) {
                e.printStackTrace()
                Log.i(TAG, "exception at line: $it")
            }
        }

        return playUrlItems
    }

    private fun sleepBySeconds(seconds: Int) {
        Log.i(TAG, "sleep seconds:$seconds")
        try {
            Thread.sleep((seconds * 1000).toLong())
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    // 截图处理，转成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)
            pngFile.delete()
        }
        else {
            Log.i(TAG, "screen shot failed")
        }
    }

    //通过ftp上传图片到服务器
    private fun uploadImage() {
        val uploadTask = ImageUploadTask(context)
        uploadTask.from(this)
        FtpManager.instance.addTask(uploadTask)
        this.uploadTask = uploadTask
    }

    /**
     * 先上报数据，截图上传继续在后台进行
     */
    private fun report() {
        Log.i(TAG, "report....status: ${this.status}")
        val postJson = JSONObject()
        val reportArray = JSONArray()
        try {
            reportArray.put(this.reportJson)
            postJson.putOpt("data", reportArray)
            reportProtocol.withBody(postJson.toString()).execute(dataHandler)
        } catch (e: Exception) {
            e.printStackTrace()
        }

        val reportPath = this.taskPath + "/" + this.imageDateFormat.format(Date()) + "_report.json"
        AppUtil.saveToFile(reportPath, postJson.toString())
    }

    override fun onProtocolFailed(p0: IProtocol?) {
        Log.i(TAG, "onProtocolFailed....")
        this.status = State.REPORT_FAILED
        finish(false)
    }

    override fun onHttpFailed(p0: IProtocol?) {
        Log.i(TAG, "onHttpFailed....")
        this.status = State.REPORT_FAILED
        finish(false)
    }

    override fun onProtocolSucceed(p0: IProtocol?) {
        Log.i(TAG, "onProtocolSucceed....")
        this.status = State.COMPLETE
        finish(true)
    }

    var taskListener: OnTaskListener? = null

    /**
     * 任务状态
     */
    interface OnTaskListener {
        /**
         * @param result true 成功完成
         */
        fun onComplete(result: Boolean)
    }

}