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 : IAppBaseCallback {

    private val TAG = "RobotTask"

    val STATUS_MAP = mapOf(
            "0" to "未处理",
            "1" to "进行中",
            "2" to "中断",
            "3" to "截图失败",
            "4" to "图片上传中",
            "5" to "上报失败",
            "6" to "处理完成",
            "7" to "手动停止")

    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
    private var status = "0"


    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 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())
    }

    fun start() {

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

        this.status = "1"
        this.prepareReport()
        // 启动应用
        this.launchApp(true)
        // 频道轮询处理
        try {
            var success = this.processChannels(0)
            // 再次尝试
            if (!success) {
                if (this.status == "7")
                    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 == "7")
                    return
            }

            // 上传图片
            this.uploadImage()
            this.status = "4"
        } catch (e: java.lang.Exception) {
            this.status = "2"
            e.printStackTrace()
        }

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

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

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

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

    fun destroy() {
        // 杀掉tcpdump进程
        this.status = "7"
        AdbUtil.killTcpdump()

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

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

    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}")
        Thread.sleep(2 * 1000L)
        // 启动2s开始抓取，避免第一个频道没有抓到
        capture(this.apkInfo.launchDelay + this.apkInfo.captureDelay)
        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()
        for (i in index + 1 until this.apkInfo.channelCount) {

            if (this.status == "7")
                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) {
        Log.i(TAG, "capture")
        val file = File(this.capturePath)
        if (file.exists())
            file.delete()
        Thread(Runnable {
            proc = AdbUtil.tcpCapture(this.capturePath)
        }).start()
        Log.i(TAG, "capture sleep $delay seconds")
        Thread.sleep(delay * 1000L)
        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"

                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 = "5"
        finish(false)
    }

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

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

    var taskListener: OnTaskListener? = null

    interface OnTaskListener {
        fun onComplete(result: Boolean)
    }

}