package win.fkey.netboost.service

import com.flashlink.vpn.CryptoUtils
import com.flashlink.vpn.VLog
import com.flashlink.vpn.XrayConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.apache.commons.io.IOUtils
import org.json.JSONArray
import org.json.JSONObject
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.util.UUID
import java.util.concurrent.atomic.AtomicInteger

/**
 * 网络请求上报类 - Kotlin 优化版本
 * 使用协程处理异步请求
 */
class NetworkReporter private constructor() {

    companion object {
        private const val TAG = "NetworkReporter"
        private const val CONNECT_TIMEOUT = 15000
        private const val READ_TIMEOUT = 15000

        @Volatile
        private var instance: NetworkReporter? = null

        fun getInstance(): NetworkReporter {
            return instance ?: synchronized(this) {
                instance ?: NetworkReporter().also { instance = it }
            }
        }
    }

    private var baseUrls: List<String> = emptyList()
    private var token: String? = null
    private var cachedJsonObject: JSONObject? = null

    // 加密相关参数
    private var aesKey: String? = null
    private var aesIv: String? = null
    private var enableEncryption = false

    // 选择节点数据
    private var locationId: Int = 0
    private var locationCode: String? = null

    private var connectedNodeId = ""

    // 动态参数
    private val peekLogIndex = AtomicInteger(0)

    // 不同模块的items参数配置
    private val itemsConfigs = HashMap<String, ItemsParamConfig>()


    fun getLocationCode() : String? {
        return locationCode
    }

    fun getConnectedNodeId() : String? {
        return connectedNodeId
    }

    data class ItemsParamConfig(
        val module: String,
        var category: String = "flashlink",
        var level: String = "info",
        val defaultFields: MutableMap<String, Any> = HashMap(),
        var useDynamicFields: Boolean = false
    ) {
        fun addDefaultField(key: String, value: Any): ItemsParamConfig {
            defaultFields[key] = value
            return this
        }
    }

    interface ReportCallback {
        fun onSuccess(response: String)
        fun onFailure(error: String)
    }

    init {
        resetPeekLogIndex()
    }

    fun initialize(xrayConfig: XrayConfig) {
        this.baseUrls = xrayConfig.baseUrls
        this.token = xrayConfig.accessToken
        resetPeekLogIndex()
        VLog.i(TAG, "NetworkReporter initialized with ${this.baseUrls.size} base URLs")
        setEncryptionParams(xrayConfig.aesKey, xrayConfig.aesIv)
        setCachedParams(xrayConfig.params)
        setLocationParams(xrayConfig.locationId, xrayConfig.locationCode)
    }

    fun setEncryptionParams(aesKey: String?, aesIv: String?) {
        this.aesKey = aesKey
        this.aesIv = aesIv
        this.enableEncryption = !aesKey.isNullOrEmpty() && !aesIv.isNullOrEmpty()
        if (this.enableEncryption) {
            VLog.i(TAG, "Encryption enabled")
        }
    }

    fun setLocationParams(locationId: Int, locationCode: String?) {
        this.locationId = locationId
        this.locationCode = locationCode
    }

    fun disableEncryption() {
        this.enableEncryption = false
        VLog.i(TAG, "Encryption disabled")
    }

    fun setCachedParams(jsonParams: String?) {
        if (jsonParams.isNullOrEmpty()) {
            this.cachedJsonObject = null
            return
        }
        try {
            this.cachedJsonObject = JSONObject(jsonParams)
        } catch (e: Exception) {
            VLog.e(TAG, "Failed to parse cached JSON parameters: ${e.message}")
            this.cachedJsonObject = null
        }
    }

    private fun configureItemsParams(module: String, config: ItemsParamConfig) {
        synchronized(itemsConfigs) {
            itemsConfigs[module] = config
        }
        VLog.i(TAG, "Configured items params for module: $module")
    }

    fun removeItemsConfig(module: String) {
        synchronized(itemsConfigs) {
            itemsConfigs.remove(module)
        }
        VLog.i(TAG, "Removed items config for module: $module")
    }

    private fun getItemsConfig(module: String): ItemsParamConfig {
        return synchronized(itemsConfigs) {
            itemsConfigs[module]
        } ?: ItemsParamConfig(module = "default")
    }

    fun addPeekLogParams(module: String, stats: String?, boostSessionId: String?) {
        try {
            val config = ItemsParamConfig(module).apply {
                useDynamicFields = true
                addDefaultField("locationId", locationId)
                addDefaultField("locationCode", locationCode ?: "")
                addDefaultField("boostSessionId", boostSessionId ?: "")
            }

            if (!stats.isNullOrEmpty()) {
                parseMaxSpeedJson(config, stats)
            }
            configureItemsParams(module, config)
            VLog.i(TAG, "Successfully parsed realtimeSpeed JSON")
        } catch (e: Exception) {
            VLog.e(TAG, "addPeekLogParams Failed to parse realtimeSpeed JSON: ${e.message}")
        }
    }

    fun addBoostResultParams(
        module: String, stats: String?, boostSessionId: String?,
        success: Boolean, code: Long
    ) {
        try {
            val config = ItemsParamConfig(module).apply {
                addDefaultField("success", success)
                addDefaultField("code", code)
                addDefaultField("locationId", locationId)
                addDefaultField("locationCode", locationCode ?: "")
                addDefaultField("boostSessionId", boostSessionId ?: "")
            }

            if (!stats.isNullOrEmpty()) {
                parseMaxSpeedJson(config, stats)
                parseConnectionHistory(config, stats)
            }
            configureItemsParams(module, config)
            VLog.i(TAG, "Successfully parsed realtimeSpeed JSON")
        } catch (e: Exception) {
            VLog.e(TAG, "addBoostResultParams Failed to parse realtimeSpeed JSON: ${e.message}")
        }
    }

    private fun parseMaxSpeedJson(config: ItemsParamConfig, stats: String) {
        val jsonObject = JSONObject(stats)
        if (jsonObject.has("maxSpeed")) {
            val maxSpeedsObj = jsonObject.getJSONObject("maxSpeed")
            val maxSpeeds = HashMap<String, Int>()
            val keys = maxSpeedsObj.keys()
            while (keys.hasNext()) {
                val key = keys.next()
                maxSpeeds[key] = maxSpeedsObj.getInt(key)
            }
            config.addDefaultField("maxSpeeds", maxSpeeds)
        }
    }

    private fun parseConnectionHistory(config: ItemsParamConfig, realtimeSpeed: String) {
        val jsonObject = JSONObject(realtimeSpeed)
        if (jsonObject.has("connectionHistory")) {
            val connectionHistoryArray = jsonObject.getJSONArray("connectionHistory")
            val connectionHistory = ArrayList<Map<String, Any>>()
            for (i in 0 until connectionHistoryArray.length()) {
                val connectionObj = connectionHistoryArray.getJSONObject(i)
                val connection = HashMap<String, Any>()
                val keys = connectionObj.keys()
                while (keys.hasNext()) {
                    val key = keys.next()
                    connection[key] = connectionObj.get(key)
                }
                connectionHistory.add(connection)
            }
            if(connectionHistory.isNotEmpty()) {
                connectedNodeId = try {
                    connectionHistory[connectionHistory.size - 1]["nodeId"].toString()
                } catch (e: Exception) {
                    ""
                }
            }
            config.addDefaultField("connectionHistory", connectionHistory)
        }
    }

    private fun resetPeekLogIndex() {
        peekLogIndex.set(0)
    }

    fun report(endpoint: String, module: String, jsonParams: String?, callback: ReportCallback?) {
        if (baseUrls.isEmpty()) {
            callback?.onFailure("No base URLs configured")
            return
        }

        // 使用 IO 协程执行请求
        CoroutineScope(Dispatchers.IO).launch {
            val jsonObject = parseJsonParams(jsonParams, callback) ?: return@launch

            var lastException: Exception? = null
            var success = false

            for ((i, baseUrl) in baseUrls.withIndex()) {
                val fullUrl = baseUrl + endpoint
                VLog.i(TAG, "Trying to report to: $fullUrl (attempt ${i + 1})")

                try {
                    val response = makeRequest(fullUrl, module, jsonObject)
                    VLog.i(TAG, "Report successful: module -> $module, url -> $fullUrl")
                    withContext(Dispatchers.Main) {
                        callback?.onSuccess(response)
                    }
                    success = true
                    break
                } catch (e: Exception) {
                    lastException = e
                    VLog.e(TAG, "Report failed: $fullUrl, error: ${e.message}")
                    if (i < baseUrls.size - 1) {
                        VLog.i(TAG, "Trying next URL...")
                    }
                }
            }

            if (!success) {
                val errorMsg = "All baseURLs failed to report: ${lastException?.message}"
                VLog.e(TAG, errorMsg)
                withContext(Dispatchers.Main) {
                    callback?.onFailure(errorMsg)
                }
            }
        }
    }

    private fun parseJsonParams(jsonParams: String?, callback: ReportCallback?): JSONObject? {
        if (jsonParams != null) {
            return try {
                JSONObject(jsonParams)
            } catch (e: Exception) {
                VLog.e(TAG, "Failed to parse JSON parameters: ${e.message}")
                callback?.onFailure("Invalid JSON parameters")
                null
            }
        } else if (cachedJsonObject != null) {
            return cachedJsonObject
        } else {
            callback?.onFailure("No JSON parameters provided")
            return null
        }
    }

    fun buildRequestParams(module: String): String? {
        return try {
            val jsonObject = JSONObject()
            val finalJsonParams = getItemsParam(jsonObject, module)
            VLog.i(TAG, "Built request parameters")
            finalJsonParams
        } catch (e: Exception) {
            VLog.e(TAG, "Failed to build request parameters: ${e.message}")
            null
        }
    }

    private fun makeRequest(urlString: String, module: String, jsonObject: JSONObject): String {
        var connection: HttpURLConnection? = null
        try {
            val url = URL(urlString)
            connection = url.openConnection() as HttpURLConnection

            connection.requestMethod = "POST"
            connection.setRequestProperty("Content-Type", "application/json")
            connection.setRequestProperty("X-NL-Product-Code", "flashlink")
            connection.setRequestProperty("X-NL-Content-Encoding", "gzip")

            token?.takeIf { it.isNotEmpty() }?.let {
                connection.setRequestProperty("Authorization", it)
            }

            connection.connectTimeout = CONNECT_TIMEOUT
            connection.readTimeout = READ_TIMEOUT
            connection.doOutput = true
            connection.doInput = true

            val finalJsonParams = insertItemsParam(jsonObject, module)

            val requestBody = if (enableEncryption && !aesKey.isNullOrEmpty() && !aesIv.isNullOrEmpty()) {
                try {
                    CryptoUtils.compressAndEncrypt(finalJsonParams, aesKey!!, aesIv!!)
                } catch (e: Exception) {
                    VLog.e(TAG, "Failed to encrypt request: ${e.message}")
                    throw IOException("Encryption failed", e)
                }
            } else {
                finalJsonParams.toByteArray(Charsets.UTF_8)
            }

            connection.outputStream.use { os ->
                os.write(requestBody)
                os.flush()
            }

            val responseCode = connection.responseCode
            VLog.i(TAG, "Response code: $responseCode")

            val inputStream = if (responseCode in 200..299) connection.inputStream else connection.errorStream
                ?: throw IOException("Response stream is null")

            val inputByte = inputStream.use { IOUtils.toByteArray(it) }

            val finalResponse = if (enableEncryption && !aesKey.isNullOrEmpty() && !aesIv.isNullOrEmpty()) {
                try {
                    CryptoUtils.decryptAndDecompress(inputByte, aesKey!!, aesIv!!)
                } catch (e: Exception) {
                    VLog.e(TAG, "Failed to decrypt response: ${e.message}")
                    throw IOException("Decryption failed", e)
                }
            } else {
                String(inputByte, Charsets.UTF_8)
            }

            if (responseCode in 200..299) {
                return finalResponse
            } else {
                throw IOException("HTTP error: $responseCode, response: $finalResponse")
            }

        } finally {
            connection?.disconnect()
        }
    }

    private fun insertItemsParam(jsonObject: JSONObject, module: String): String {
        return try {
            val item = createItemObject(module)
            val items = JSONArray().apply { put(item) }
            jsonObject.put("items", items)
            jsonObject.toString()
        } catch (e: Exception) {
            VLog.e(TAG, "Failed to insert items parameter: ${e.message}")
            jsonObject.toString()
        }
    }

    private fun getItemsParam(jsonObject: JSONObject, module: String): String {
        return try {
            val item = createItemObject(module)
            val items = JSONArray().apply { put(item) }
            items.toString()
        } catch (e: Exception) {
            VLog.e(TAG, "Failed to get items parameter: ${e.message}")
            jsonObject.toString()
        }
    }

    private fun createItemObject(module: String): JSONObject {
        val config = getItemsConfig(module)

        val currentIndex = if (config.useDynamicFields) peekLogIndex.incrementAndGet() else 0
        val dynamicId = UUID.randomUUID().toString()
        val time = System.currentTimeMillis()

        val item = JSONObject().apply {
            put("id", dynamicId)
            put("time", time)
            put("level", config.level)
            put("module", config.module)
            put("category", config.category)
        }

        val fields = JSONObject()
        addDefaultFields(fields, config.defaultFields)

        if (config.useDynamicFields) {
            fields.put("peekLogIndex", currentIndex)
        }
        fields.put("generatedTime", time)

        item.put("fields", fields)
        return item
    }

    private fun addDefaultFields(fields: JSONObject, defaultFields: Map<String, Any>) {
        defaultFields.forEach { (key, value) ->
            when (value) {
                is Map<*, *> -> {
                    val jsonValue = JSONObject()
                    (value as Map<String, Any>).forEach { (k, v) ->
                        jsonValue.put(k, v)
                    }
                    fields.put(key, jsonValue)
                }
                is List<*> -> {
                    val jsonArray = JSONArray()
                    (value as List<Any>).forEach { item ->
                        if (item is Map<*, *>) {
                            val jsonItem = JSONObject()
                            (item as Map<String, Any>).forEach { (k, v) ->
                                jsonItem.put(k, v)
                            }
                            jsonArray.put(jsonItem)
                        } else {
                            jsonArray.put(item)
                        }
                    }
                    fields.put(key, jsonArray)
                }
                else -> {
                    fields.put(key, value)
                }
            }
        }
    }
}

