package com.example.vastlib.service

import android.os.Build
import android.util.Base64.encodeToString
import com.example.vastlib.entity.action.*
import com.example.vastlib.entity.report.ActionExec
import com.example.vastlib.entity.task.TaskConfig
import com.example.vastlib.utils.HttpUtils
import com.example.vastlib.utils.JSONUtils.toJsonString
import com.example.vastlib.utils.LogUtils
import com.example.vastlib.utils.WebSocketClientManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import org.json.JSONArray
import java.net.URI
import java.net.URL
import java.net.URLEncoder
import java.util.*

abstract class BaseActionExecService(protected open val taskConfig: TaskConfig) {
    protected val scope:CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    //执行http_action产生的错误码
   companion object {
        val ERROR_CODE_HTTP_ACTION_EXEC_FAILED = 600
        val ERROR_CODE_HTTP_BUILD_REDIRECT_FAILED = 615

        //执行pin_action产生的错误码
        val ERROR_CODE_PIN_ACTION_EXEC_FAILED = 700

        var ERROR_CODE_PIN_ACTION_TIMEOUT = 702

        //执行web_socket_action产生的错误码
        val WEB_SOCKET_CODE = 101
        val ERROR_CODE_WS_ACTION_EXEC_FAILED = 800

        //action通用错误码
        val ASYNC_EXEC_CODE = 900
    }

    protected fun replaceVariableData(
        nameValues: List<NameValue>
    ) {
        nameValues.map {
                it.name = getVariableData(it.name)
                it.value = getVariableData(it.value)
        }
    }

    protected fun getVariableData(containVariableData: String): String {
        if (containVariableData.isBlank()) return containVariableData
        var realData = containVariableData
        for (variables in taskConfig.variable_cache) {
            val searchData = "{${variables.key}}"
            val replaceData = variables.value
            if (containVariableData.contains(searchData) && replaceData.isNotBlank()) {
                realData = realData.replace(searchData, replaceData)
            }
        }
        return realData
    }

    protected fun cookieFromCookieManager(url: String): Set<NameValue> {
        val cookiesCache: MutableSet<NameValue> = mutableSetOf()
        val headers: MutableMap<String, List<String>> = mutableMapOf()
        URL(url).runCatching {
            URI(protocol, host, path, query, null).runCatching {
                taskConfig.cookie_manager.get(this, headers).runCatching {
                    val cookieList =
                        getOrElse(HttpUtils.Params.REQUEST_HEADER_COOKIE) { emptyList() }
                    val cookies = cookieList.firstOrNull()
                    if (!cookies.isNullOrBlank()) {
                        cookies.split("; ").forEach { cookie ->
                            val kv = cookie.split("=", limit = 2)
                            if (kv.size >= 2) {
                                cookiesCache += NameValue(kv[0], kv[1])
                            }
                        }
                    }
                }
            }
        }
        LogUtils.info("getCookie $url : $cookiesCache")
        return cookiesCache
    }

    protected fun urlEncode(input: String): String? = kotlin.runCatching {
        URLEncoder.encode(input, "UTF-8")
    }.getOrNull()

    protected fun nameValueToMap(nameValues: List<NameValue>): Map<String, String> =
        nameValues.associate {
            it.name to it.value
        }

    protected fun extractBodyVariableToCache(
        action: BaseAction,
        wholeResponseData: String,
        responseBody: ByteArray,
    ) {
        runCatching {
            when (action.type) {
                BaseAction.ACTION_TYPE_HTTP -> {
                    val httpAction = action as? HttpAction
                    val actionResponse = httpAction?.response
                    actionResponse?.params?.toMutableList()
                }
                BaseAction.ACTION_TYPE_WS -> {
                    val socketAction = action as? WebSocketAction
                    socketAction?.response?.params?.toMutableList()
                }
                else -> null
            }?.forEach { extractRule ->
                val value = when (extractRule.rule) {
                    VarExtractRule.EXTRACT_RULE_RESPONSE -> {
                        String(responseBody)
                    }
                    VarExtractRule.EXTRACT_RULE_WHOLE -> {
                        wholeResponseData
                    }
                    VarExtractRule.EXTRACT_RULE_BETWEEN -> {
                        val expr = extractRule.expr.split("|").take(2)
                        wholeResponseData.substringAfter(expr.first()).substringBefore(expr.last())
                    }
                    VarExtractRule.EXTRACT_RULE_REGEXP -> {
                        Regex(extractRule.expr).find(wholeResponseData)?.groupValues?.getOrNull(1) ?: ""
                    }
                    VarExtractRule.EXTRACT_RULE_BASE64 -> {
                        if (Build.VERSION.SDK_INT >= 26) {
                            Base64.getEncoder().encodeToString(responseBody)
                        } else {
                            encodeToString(responseBody, android.util.Base64.DEFAULT)
                        }
                    }
                    else -> {
                        ""
                    }
                }
                taskConfig.variable_cache[extractRule.variable] = value
            }
        }

    }

    protected fun amendBaseHeader(
        headers: List<NameValue>,
        includeAccept: Boolean
    ): List<NameValue> {
        val headers = headers.toMutableList()
        if (headers.isEmpty()) {
            headers += NameValue(HttpUtils.Params.REQUEST_HEADER_USER_AGENT, taskConfig.user_agent)
            if (includeAccept) {
                headers += NameValue(HttpUtils.Params.REQUEST_HEADER_ACCEPT, taskConfig.accept)
            }
            headers += NameValue(
                HttpUtils.Params.REQUEST_HEADER_ACCEPT_LANGUAGE,
                taskConfig.accept_language
            )
        } else {
            var hasUserAgent = false
            var hasAcceptLanguage = false
            var hasAccept = false
            headers.forEach { header ->
                if (HttpUtils.Params.REQUEST_HEADER_USER_AGENT == header.name) {
                    hasUserAgent = true
                }
                if (HttpUtils.Params.REQUEST_HEADER_ACCEPT == header.name) {
                    hasAccept = true
                }
                if (HttpUtils.Params.REQUEST_HEADER_ACCEPT_LANGUAGE == header.name) {
                    hasAcceptLanguage = true
                }
            }
            if (!hasUserAgent) {
                headers += NameValue(
                    HttpUtils.Params.REQUEST_HEADER_USER_AGENT,
                    taskConfig.user_agent
                )
            }
            if (!hasAccept && includeAccept) {
                headers += NameValue(HttpUtils.Params.REQUEST_HEADER_ACCEPT, taskConfig.accept)
            }
            if (!hasAcceptLanguage) {
                headers += NameValue(
                    HttpUtils.Params.REQUEST_HEADER_ACCEPT_LANGUAGE,
                    taskConfig.accept_language
                )
            }
        }
        return headers
    }

    protected fun genBaseHeaderMap(taskConfig: TaskConfig): Map<String, String> {
        val headers: MutableList<NameValue> = mutableListOf()
        headers += NameValue(HttpUtils.Params.REQUEST_HEADER_USER_AGENT, taskConfig.user_agent)
        headers += NameValue(HttpUtils.Params.REQUEST_HEADER_ACCEPT, taskConfig.accept)
        headers += NameValue(
            HttpUtils.Params.REQUEST_HEADER_ACCEPT_LANGUAGE,
            taskConfig.accept_language
        )
        return nameValueToMap(headers)
    }

    protected fun buildCookie(cookies: List<NameValue>): NameValue {
        val cookieList = mutableListOf<String>()
        cookies.forEach {
            cookieList += "${it.name}=${it.value}"
        }
        return NameValue(
            name = HttpUtils.Params.REQUEST_HEADER_COOKIE,
            value = cookieList.joinToString("; ")
        )
    }

    protected fun genExceptionActionExec(
        action: BaseAction,
        errorCode: Int,
        exceptionMessage: String
    ): ActionExec {
        val actionExec = ActionExec()
        actionExec.step = taskConfig.current_step
        actionExec.index = 1
        actionExec.time = System.currentTimeMillis()
        when (action.type) {
            BaseAction.ACTION_TYPE_HTTP -> {
                val httpAction = action as? HttpAction
                httpAction?.request?.apply {
                    actionExec.url = url
                    actionExec.method = method
                    actionExec.reqHeader = headers.toJsonString()
                    if (data.isNotBlank()) {
                        actionExec.reqData = data
                    }
                }

            }
            BaseAction.ACTION_TYPE_WS -> {
                val wsAction = action as? WebSocketAction
                wsAction?.request?.apply {
                    actionExec.url = url
                    actionExec.method = WebSocketClientManager.WEB_SOCKET_REQUEST_METHOD
                    actionExec.reqHeader = headers.toJsonString()
                    if (params.isNotEmpty()) {
                        val messages = JSONArray()
                        params.forEach {
                            messages.put(it.value)
                        }
                        actionExec.reqData = messages.toString()
                    }
                }
            }
            else -> {
                LogUtils.info("unknown action")
            }
        }
        actionExec.respCode = errorCode
        actionExec.reqData = exceptionMessage
        actionExec.cost = 0
        return actionExec
    }

    protected fun getNextStepIndex(
        nextList: List<Next>,
        wholeResponse: String,
        currentStep: Int
    ): Int {
        var currentStep = currentStep
        if (nextList.isNotEmpty() && wholeResponse.isNotBlank()) {
            var match = false
            nextList.filter { it.step > 0 }.forEach {
                if (it.contain.isNotBlank() && wholeResponse.contains(it.contain)) {
                    currentStep = it.step
                    match = true
                    return@forEach
                }
                if (it.regexp.isNotBlank()) {
                    Regex(it.regexp).find(wholeResponse)?.apply {
                        match = true
                        currentStep = it.step
                        return@forEach
                    }

                }
            }
            if (!match) {
                currentStep = Int.MAX_VALUE
            }
        } else {
            currentStep++
        }
        return currentStep
    }


}