package com.durian.tssparty.util

import com.durian.tssparty.domain.model.ERC20Selectors
import com.durian.tssparty.domain.model.EnergyPointsToken
import com.durian.tssparty.domain.model.FuturePointsToken
import com.durian.tssparty.domain.model.GreenPointsToken
import com.durian.tssparty.domain.model.TokenConfig
import com.durian.tssparty.domain.model.TokenType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.bouncycastle.jcajce.provider.digest.Keccak
import java.math.BigDecimal
import java.math.BigInteger
import java.util.concurrent.TimeUnit

/**
 * Transaction utilities for Kava EVM
 * Matches service-party-app/src/utils/transaction.ts
 */
object TransactionUtils {

    /**
     * HTTP client for blockchain RPC calls
     *
     * 【架构安全修复 - 配置连接池防止资源泄漏】
     *
     * 配置连接池参数限制资源占用:
     * - maxIdleConnections: 5 (最多保留 5 个空闲连接)
     * - keepAliveDuration: 5 分钟 (空闲连接保活时间)
     *
     * 注意: TransactionUtils 是 object 单例,生命周期与应用一致
     * 如果应用需要完全清理资源,可调用 cleanup() 方法
     */
    private val client = OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .connectionPool(okhttp3.ConnectionPool(
            maxIdleConnections = 5,
            keepAliveDuration = 5,
            timeUnit = TimeUnit.MINUTES
        ))
        .build()

    private val jsonMediaType = "application/json; charset=utf-8".toMediaType()

    /**
     * Cleanup OkHttpClient resources
     *
     * 【架构安全修复 - 提供资源清理方法】
     *
     * 虽然 TransactionUtils 是 object 单例,但提供此方法允许:
     * 1. 测试环境清理资源
     * 2. 应用完全退出时释放资源
     * 3. 内存紧张时主动清理
     */
    fun cleanup() {
        try {
            client.connectionPool.evictAll()
            client.dispatcher.executorService.shutdown()
            client.cache?.close()
        } catch (e: Exception) {
            // 静默失败,因为这是清理操作
        }
    }

    // Chain IDs
    const val KAVA_TESTNET_CHAIN_ID = 2221
    const val KAVA_MAINNET_CHAIN_ID = 2222

    /**
     * Prepared transaction ready for signing
     */
    data class PreparedTransaction(
        val nonce: BigInteger,
        val gasPrice: BigInteger,
        val gasLimit: BigInteger,
        val to: String,
        val from: String,
        val value: BigInteger,
        val data: ByteArray = ByteArray(0),
        val chainId: Int,
        val signHash: String,  // Hash to be signed (hex with 0x prefix)
        val rawTxForSigning: ByteArray  // RLP encoded tx for signing
    )

    /**
     * Transaction parameters for preparation
     */
    data class TransactionParams(
        val from: String,
        val to: String,
        val amount: String,  // In KAVA or token units (not wei)
        val rpcUrl: String,
        val chainId: Int = KAVA_TESTNET_CHAIN_ID,
        val tokenType: TokenType = TokenType.KAVA  // Token type for transfer
    )

    /**
     * Prepare a transaction for signing
     * Gets nonce, gas price, estimates gas, and calculates sign hash
     * Supports both native KAVA transfers and ERC-20 token transfers (绿积分/积分股/积分值)
     */
    suspend fun prepareTransaction(params: TransactionParams): Result<PreparedTransaction> = withContext(Dispatchers.IO) {
        try {
            // 1. Get nonce
            val nonce = getNonce(params.from, params.rpcUrl).getOrThrow()

            // 2. Get gas price
            val gasPrice = getGasPrice(params.rpcUrl).getOrThrow()

            // 3. Prepare transaction based on token type
            val (toAddress, valueWei, txData) = when (params.tokenType) {
                TokenType.KAVA -> {
                    // Native KAVA transfer
                    Triple(params.to, kavaToWei(params.amount), ByteArray(0))
                }
                TokenType.GREEN_POINTS, TokenType.ENERGY_POINTS, TokenType.FUTURE_POINTS -> {
                    // ERC-20 token transfer
                    // To address is the contract, value is 0
                    // Data is transfer(recipient, amount) encoded
                    val contractAddress = TokenConfig.getContractAddress(params.tokenType)
                        ?: return@withContext Result.failure(Exception("Invalid token type"))
                    val decimals = TokenConfig.getDecimals(params.tokenType)
                    val tokenAmount = tokenToRaw(params.amount, decimals)
                    val transferData = encodeErc20Transfer(params.to, tokenAmount)
                    Triple(contractAddress, BigInteger.ZERO, transferData)
                }
            }

            // 4. Estimate gas
            val gasLimit = estimateGasWithData(
                from = params.from,
                to = toAddress,
                value = valueWei,
                data = txData,
                rpcUrl = params.rpcUrl
            ).getOrElse {
                // Default gas limits
                when (params.tokenType) {
                    TokenType.KAVA -> BigInteger.valueOf(21000)
                    else -> BigInteger.valueOf(65000)  // ERC-20 transfers need more gas
                }
            }

            // 5. RLP encode for signing (Legacy Type 0 format)
            // Format: [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]
            val rawTxForSigning = rlpEncodeForSigning(
                nonce = nonce,
                gasPrice = gasPrice,
                gasLimit = gasLimit,
                to = toAddress,
                value = valueWei,
                data = txData,
                chainId = params.chainId
            )

            // 6. Calculate Keccak-256 hash
            val signHash = keccak256(rawTxForSigning)

            Result.success(PreparedTransaction(
                nonce = nonce,
                gasPrice = gasPrice,
                gasLimit = gasLimit,
                to = toAddress,
                from = params.from,
                value = valueWei,
                data = txData,
                chainId = params.chainId,
                signHash = "0x" + signHash.toHexString(),
                rawTxForSigning = rawTxForSigning
            ))
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    /**
     * Encode ERC-20 transfer(address,uint256) function call
     */
    private fun encodeErc20Transfer(to: String, amount: BigInteger): ByteArray {
        // Function selector: transfer(address,uint256) = 0xa9059cbb
        val selector = ERC20Selectors.TRANSFER.removePrefix("0x").hexToByteArray()

        // Encode recipient address (padded to 32 bytes)
        val paddedAddress = to.removePrefix("0x").lowercase().padStart(64, '0').hexToByteArray()

        // Encode amount (padded to 32 bytes)
        val amountHex = amount.toString(16).padStart(64, '0')
        val paddedAmount = amountHex.hexToByteArray()

        return selector + paddedAddress + paddedAmount
    }

    /**
     * Convert token amount to raw units based on decimals
     * @param amount Human-readable amount (e.g., "100.5")
     * @param decimals Token decimals (e.g., 6 for USDT-like tokens, 18 for native)
     */
    fun tokenToRaw(amount: String, decimals: Int): BigInteger {
        val decimal = BigDecimal(amount)
        val multiplier = BigDecimal.TEN.pow(decimals)
        val rawDecimal = decimal.multiply(multiplier)
        return rawDecimal.toBigInteger()
    }

    /**
     * Convert raw units to human-readable token amount
     * @param raw Raw amount in smallest units
     * @param decimals Token decimals (e.g., 6 for USDT-like tokens, 18 for native)
     */
    fun rawToToken(raw: BigInteger, decimals: Int): String {
        val rawDecimal = BigDecimal(raw)
        val divisor = BigDecimal.TEN.pow(decimals)
        val displayDecimal = rawDecimal.divide(divisor, decimals, java.math.RoundingMode.DOWN)
        return displayDecimal.toPlainString()
    }

    /**
     * Convert Green Points amount to raw units (6 decimals)
     * @deprecated Use tokenToRaw(amount, 6) instead
     */
    fun greenPointsToRaw(amount: String): BigInteger {
        return tokenToRaw(amount, GreenPointsToken.DECIMALS)
    }

    /**
     * Convert raw units to Green Points display amount
     * @deprecated Use rawToToken(raw, 6) instead
     */
    fun rawToGreenPoints(raw: BigInteger): String {
        return rawToToken(raw, GreenPointsToken.DECIMALS)
    }

    /**
     * Finalize transaction with signature
     * Returns the signed raw transaction hex string ready for broadcast
     */
    fun finalizeTransaction(
        preparedTx: PreparedTransaction,
        r: ByteArray,
        s: ByteArray,
        recoveryId: Int
    ): String {
        // Calculate EIP-155 v value
        // v = chainId * 2 + 35 + recovery_id
        val v = preparedTx.chainId * 2 + 35 + recoveryId

        // RLP encode signed transaction
        // Format: [nonce, gasPrice, gasLimit, to, value, data, v, r, s]
        val signedTx = rlpEncodeSigned(
            nonce = preparedTx.nonce,
            gasPrice = preparedTx.gasPrice,
            gasLimit = preparedTx.gasLimit,
            to = preparedTx.to,
            value = preparedTx.value,
            data = preparedTx.data,
            v = BigInteger.valueOf(v.toLong()),
            r = BigInteger(1, r),
            s = BigInteger(1, s)
        )

        return "0x" + signedTx.toHexString()
    }

    /**
     * Broadcast signed transaction to the network
     */
    suspend fun broadcastTransaction(signedTx: String, rpcUrl: String): Result<String> = withContext(Dispatchers.IO) {
        try {
            val requestBody = """
                {
                    "jsonrpc": "2.0",
                    "method": "eth_sendRawTransaction",
                    "params": ["$signedTx"],
                    "id": 1
                }
            """.trimIndent()

            val request = Request.Builder()
                .url(rpcUrl)
                .post(requestBody.toRequestBody(jsonMediaType))
                .build()

            val response = client.newCall(request).execute()
            val responseBody = response.body?.string() ?: return@withContext Result.failure(Exception("Empty response"))

            val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
            if (json.has("error")) {
                val errorMsg = json.get("error").asJsonObject.get("message").asString
                return@withContext Result.failure(Exception(errorMsg))
            }

            val txHash = json.get("result").asString
            Result.success(txHash)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    /**
     * Get transaction receipt (for confirmation)
     */
    suspend fun getTransactionReceipt(txHash: String, rpcUrl: String): Result<TransactionReceipt?> = withContext(Dispatchers.IO) {
        try {
            val requestBody = """
                {
                    "jsonrpc": "2.0",
                    "method": "eth_getTransactionReceipt",
                    "params": ["$txHash"],
                    "id": 1
                }
            """.trimIndent()

            val request = Request.Builder()
                .url(rpcUrl)
                .post(requestBody.toRequestBody(jsonMediaType))
                .build()

            val response = client.newCall(request).execute()
            val responseBody = response.body?.string() ?: return@withContext Result.failure(Exception("Empty response"))

            val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
            if (json.has("error")) {
                val errorMsg = json.get("error").asJsonObject.get("message").asString
                return@withContext Result.failure(Exception(errorMsg))
            }

            val result = json.get("result")
            if (result.isJsonNull) {
                // Transaction not yet mined
                return@withContext Result.success(null)
            }

            val receipt = result.asJsonObject
            Result.success(TransactionReceipt(
                transactionHash = receipt.get("transactionHash").asString,
                blockNumber = receipt.get("blockNumber").asString,
                status = receipt.get("status").asString == "0x1",
                gasUsed = BigInteger(receipt.get("gasUsed").asString.removePrefix("0x"), 16)
            ))
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    data class TransactionReceipt(
        val transactionHash: String,
        val blockNumber: String,
        val status: Boolean,
        val gasUsed: BigInteger
    )

    // ========== RPC Methods ==========

    private suspend fun getNonce(address: String, rpcUrl: String): Result<BigInteger> = withContext(Dispatchers.IO) {
        try {
            val requestBody = """
                {
                    "jsonrpc": "2.0",
                    "method": "eth_getTransactionCount",
                    "params": ["$address", "pending"],
                    "id": 1
                }
            """.trimIndent()

            val request = Request.Builder()
                .url(rpcUrl)
                .post(requestBody.toRequestBody(jsonMediaType))
                .build()

            val response = client.newCall(request).execute()
            val responseBody = response.body?.string() ?: return@withContext Result.failure(Exception("Empty response"))

            val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
            if (json.has("error")) {
                return@withContext Result.failure(Exception(json.get("error").asJsonObject.get("message").asString))
            }

            val hexNonce = json.get("result").asString
            Result.success(BigInteger(hexNonce.removePrefix("0x"), 16))
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    private suspend fun getGasPrice(rpcUrl: String): Result<BigInteger> = withContext(Dispatchers.IO) {
        try {
            val requestBody = """
                {
                    "jsonrpc": "2.0",
                    "method": "eth_gasPrice",
                    "params": [],
                    "id": 1
                }
            """.trimIndent()

            val request = Request.Builder()
                .url(rpcUrl)
                .post(requestBody.toRequestBody(jsonMediaType))
                .build()

            val response = client.newCall(request).execute()
            val responseBody = response.body?.string() ?: return@withContext Result.failure(Exception("Empty response"))

            val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
            if (json.has("error")) {
                return@withContext Result.failure(Exception(json.get("error").asJsonObject.get("message").asString))
            }

            val hexGasPrice = json.get("result").asString
            Result.success(BigInteger(hexGasPrice.removePrefix("0x"), 16))
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    private suspend fun estimateGas(
        from: String,
        to: String,
        value: BigInteger,
        rpcUrl: String
    ): Result<BigInteger> = withContext(Dispatchers.IO) {
        estimateGasWithData(from, to, value, ByteArray(0), rpcUrl)
    }

    private suspend fun estimateGasWithData(
        from: String,
        to: String,
        value: BigInteger,
        data: ByteArray,
        rpcUrl: String
    ): Result<BigInteger> = withContext(Dispatchers.IO) {
        try {
            val valueHex = "0x" + value.toString(16)
            val dataHex = if (data.isEmpty()) "" else "\"data\": \"0x${data.toHexString()}\","
            val requestBody = """
                {
                    "jsonrpc": "2.0",
                    "method": "eth_estimateGas",
                    "params": [{
                        "from": "$from",
                        "to": "$to",
                        "value": "$valueHex"${if (dataHex.isNotEmpty()) ",\n                        ${dataHex.trimEnd(',')}" else ""}
                    }],
                    "id": 1
                }
            """.trimIndent()

            val request = Request.Builder()
                .url(rpcUrl)
                .post(requestBody.toRequestBody(jsonMediaType))
                .build()

            val response = client.newCall(request).execute()
            val responseBody = response.body?.string() ?: return@withContext Result.failure(Exception("Empty response"))

            val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
            if (json.has("error")) {
                return@withContext Result.failure(Exception(json.get("error").asJsonObject.get("message").asString))
            }

            val hexGas = json.get("result").asString
            // Add 10% buffer
            val gas = BigInteger(hexGas.removePrefix("0x"), 16)
            val gasWithBuffer = gas.multiply(BigInteger.valueOf(110)).divide(BigInteger.valueOf(100))
            Result.success(gasWithBuffer)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    // ========== Utility Methods ==========

    fun kavaToWei(kava: String): BigInteger {
        val decimal = BigDecimal(kava)
        val weiDecimal = decimal.multiply(BigDecimal("1000000000000000000"))
        return weiDecimal.toBigInteger()
    }

    fun weiToKava(wei: BigInteger): String {
        val weiDecimal = BigDecimal(wei)
        val kavaDecimal = weiDecimal.divide(BigDecimal("1000000000000000000"), 6, java.math.RoundingMode.DOWN)
        return kavaDecimal.toPlainString()
    }

    fun weiToGwei(wei: BigInteger): String {
        val weiDecimal = BigDecimal(wei)
        val gweiDecimal = weiDecimal.divide(BigDecimal("1000000000"), 2, java.math.RoundingMode.DOWN)
        return gweiDecimal.toPlainString()
    }

    /**
     * Calculate maximum transferable amount after deducting gas fee
     * @param balance Current balance in KAVA
     * @param rpcUrl RPC endpoint URL
     * @return Maximum amount in KAVA string, or "0" if insufficient balance
     */
    suspend fun calculateMaxTransferAmount(balance: String, rpcUrl: String): Result<String> = withContext(Dispatchers.IO) {
        try {
            val balanceKava = BigDecimal(balance)
            if (balanceKava <= BigDecimal.ZERO) {
                return@withContext Result.success("0")
            }

            // Get current gas price
            val gasPriceResult = getGasPrice(rpcUrl)
            val gasPrice = gasPriceResult.getOrElse {
                // Default to 1 gwei if failed
                BigInteger.valueOf(1000000000)
            }

            // Add 10% buffer to gas price
            val gasPriceWithBuffer = gasPrice.multiply(BigInteger.valueOf(110)).divide(BigInteger.valueOf(100))

            // Simple transfer gas limit is 21000
            val gasLimit = BigInteger.valueOf(21000)
            val gasFee = gasPriceWithBuffer.multiply(gasLimit)

            // Convert gas fee to KAVA
            val gasFeeKava = BigDecimal(gasFee).divide(BigDecimal("1000000000000000000"), 8, java.math.RoundingMode.UP)

            // Calculate max amount = balance - gas fee
            val maxAmount = balanceKava.subtract(gasFeeKava)

            if (maxAmount <= BigDecimal.ZERO) {
                return@withContext Result.success("0")
            }

            // Round down to 6 decimal places
            val formattedMax = maxAmount.setScale(6, java.math.RoundingMode.DOWN).stripTrailingZeros().toPlainString()
            Result.success(formattedMax)
        } catch (e: Exception) {
            // Fallback: use default gas estimate (21000 * 1 gwei = 0.000021 KAVA)
            try {
                val balanceKava = BigDecimal(balance)
                val defaultGasFee = BigDecimal("0.000021")
                val maxAmount = balanceKava.subtract(defaultGasFee)
                if (maxAmount <= BigDecimal.ZERO) {
                    Result.success("0")
                } else {
                    val formattedMax = maxAmount.setScale(6, java.math.RoundingMode.DOWN).stripTrailingZeros().toPlainString()
                    Result.success(formattedMax)
                }
            } catch (e2: Exception) {
                Result.failure(e)
            }
        }
    }

    private fun keccak256(data: ByteArray): ByteArray {
        val keccak = Keccak.Digest256()
        return keccak.digest(data)
    }

    // ========== RLP Encoding ==========

    /**
     * RLP encode transaction for signing (EIP-155)
     * Format: [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]
     */
    private fun rlpEncodeForSigning(
        nonce: BigInteger,
        gasPrice: BigInteger,
        gasLimit: BigInteger,
        to: String,
        value: BigInteger,
        data: ByteArray,
        chainId: Int
    ): ByteArray {
        val items = listOf(
            rlpEncodeInteger(nonce),
            rlpEncodeInteger(gasPrice),
            rlpEncodeInteger(gasLimit),
            rlpEncodeAddress(to),
            rlpEncodeInteger(value),
            rlpEncodeBytes(data),
            rlpEncodeInteger(BigInteger.valueOf(chainId.toLong())),
            rlpEncodeInteger(BigInteger.ZERO),
            rlpEncodeInteger(BigInteger.ZERO)
        )
        return rlpEncodeList(items)
    }

    /**
     * RLP encode signed transaction
     * Format: [nonce, gasPrice, gasLimit, to, value, data, v, r, s]
     */
    private fun rlpEncodeSigned(
        nonce: BigInteger,
        gasPrice: BigInteger,
        gasLimit: BigInteger,
        to: String,
        value: BigInteger,
        data: ByteArray,
        v: BigInteger,
        r: BigInteger,
        s: BigInteger
    ): ByteArray {
        val items = listOf(
            rlpEncodeInteger(nonce),
            rlpEncodeInteger(gasPrice),
            rlpEncodeInteger(gasLimit),
            rlpEncodeAddress(to),
            rlpEncodeInteger(value),
            rlpEncodeBytes(data),
            rlpEncodeInteger(v),
            rlpEncodeInteger(r),
            rlpEncodeInteger(s)
        )
        return rlpEncodeList(items)
    }

    private fun rlpEncodeInteger(value: BigInteger): ByteArray {
        if (value == BigInteger.ZERO) {
            return byteArrayOf(0x80.toByte())
        }
        val bytes = value.toByteArray()
        // Remove leading zero if present
        val trimmed = if (bytes[0] == 0.toByte() && bytes.size > 1) {
            bytes.copyOfRange(1, bytes.size)
        } else {
            bytes
        }
        return rlpEncodeBytes(trimmed)
    }

    private fun rlpEncodeAddress(address: String): ByteArray {
        val cleanAddress = address.removePrefix("0x")
        val bytes = cleanAddress.hexToByteArray()
        return rlpEncodeBytes(bytes)
    }

    private fun rlpEncodeBytes(bytes: ByteArray): ByteArray {
        return when {
            bytes.size == 1 && bytes[0].toInt() and 0xFF < 0x80 -> bytes
            bytes.size <= 55 -> {
                val result = ByteArray(1 + bytes.size)
                result[0] = (0x80 + bytes.size).toByte()
                System.arraycopy(bytes, 0, result, 1, bytes.size)
                result
            }
            else -> {
                val lengthBytes = bytes.size.toBigInteger().toByteArray().let { arr ->
                    if (arr[0] == 0.toByte() && arr.size > 1) arr.copyOfRange(1, arr.size) else arr
                }
                val result = ByteArray(1 + lengthBytes.size + bytes.size)
                result[0] = (0xB7 + lengthBytes.size).toByte()
                System.arraycopy(lengthBytes, 0, result, 1, lengthBytes.size)
                System.arraycopy(bytes, 0, result, 1 + lengthBytes.size, bytes.size)
                result
            }
        }
    }

    private fun rlpEncodeList(items: List<ByteArray>): ByteArray {
        val concatenated = items.fold(ByteArray(0)) { acc, item -> acc + item }
        return when {
            concatenated.size <= 55 -> {
                val result = ByteArray(1 + concatenated.size)
                result[0] = (0xC0 + concatenated.size).toByte()
                System.arraycopy(concatenated, 0, result, 1, concatenated.size)
                result
            }
            else -> {
                val lengthBytes = concatenated.size.toBigInteger().toByteArray().let { arr ->
                    if (arr[0] == 0.toByte() && arr.size > 1) arr.copyOfRange(1, arr.size) else arr
                }
                val result = ByteArray(1 + lengthBytes.size + concatenated.size)
                result[0] = (0xF7 + lengthBytes.size).toByte()
                System.arraycopy(lengthBytes, 0, result, 1, lengthBytes.size)
                System.arraycopy(concatenated, 0, result, 1 + lengthBytes.size, concatenated.size)
                result
            }
        }
    }

    // ========== Extension Functions ==========

    private fun ByteArray.toHexString(): String = joinToString("") { "%02x".format(it) }

    private fun String.hexToByteArray(): ByteArray {
        val len = this.length
        val data = ByteArray(len / 2)
        var i = 0
        while (i < len) {
            data[i / 2] = ((Character.digit(this[i], 16) shl 4) + Character.digit(this[i + 1], 16)).toByte()
            i += 2
        }
        return data
    }
}
