package io.synor.contract

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.delay
import kotlinx.serialization.json.*
import java.io.Closeable
import java.net.URLEncoder
import java.util.concurrent.atomic.AtomicBoolean

/**
 * Synor Contract SDK client for Kotlin.
 * Smart contract deployment, interaction, and event handling.
 */
class ContractClient(private val config: ContractConfig) : Closeable {
    private val closed = AtomicBoolean(false)
    private val json = Json { ignoreUnknownKeys = true }

    private val client = HttpClient(CIO) {
        install(ContentNegotiation) {
            json(json)
        }
        install(HttpTimeout) {
            requestTimeoutMillis = config.timeoutMs
            connectTimeoutMillis = config.timeoutMs
        }
        defaultRequest {
            header("Authorization", "Bearer ${config.apiKey}")
            header("X-SDK-Version", "kotlin/$VERSION")
            contentType(ContentType.Application.Json)
        }
    }

    // ==================== Contract Deployment ====================

    suspend fun deploy(options: DeployContractOptions): DeploymentResult {
        val body = buildJsonObject {
            put("bytecode", options.bytecode)
            options.abi?.let { put("abi", json.encodeToJsonElement(it)) }
            options.constructorArgs?.let { put("constructor_args", json.encodeToJsonElement(it)) }
            options.value?.let { put("value", it) }
            options.gasLimit?.let { put("gas_limit", it) }
            options.gasPrice?.let { put("gas_price", it) }
            options.nonce?.let { put("nonce", it) }
        }
        return post("/contract/deploy", body)
    }

    suspend fun deployCreate2(options: DeployContractOptions, salt: String): DeploymentResult {
        val body = buildJsonObject {
            put("bytecode", options.bytecode)
            put("salt", salt)
            options.abi?.let { put("abi", json.encodeToJsonElement(it)) }
            options.constructorArgs?.let { put("constructor_args", json.encodeToJsonElement(it)) }
            options.value?.let { put("value", it) }
            options.gasLimit?.let { put("gas_limit", it) }
            options.gasPrice?.let { put("gas_price", it) }
        }
        return post("/contract/deploy/create2", body)
    }

    suspend fun predictAddress(bytecode: String, salt: String, deployer: String? = null): String {
        val body = buildJsonObject {
            put("bytecode", bytecode)
            put("salt", salt)
            deployer?.let { put("deployer", it) }
        }
        val response: JsonObject = post("/contract/predict-address", body)
        return response["address"]?.jsonPrimitive?.content ?: throw ContractException("Missing address")
    }

    // ==================== Contract Interaction ====================

    suspend fun call(options: CallContractOptions): JsonElement {
        val body = buildJsonObject {
            put("contract", options.contract)
            put("method", options.method)
            put("args", json.encodeToJsonElement(options.args))
            put("abi", json.encodeToJsonElement(options.abi))
        }
        return post("/contract/call", body)
    }

    suspend fun send(options: SendContractOptions): TransactionResult {
        val body = buildJsonObject {
            put("contract", options.contract)
            put("method", options.method)
            put("args", json.encodeToJsonElement(options.args))
            put("abi", json.encodeToJsonElement(options.abi))
            options.value?.let { put("value", it) }
            options.gasLimit?.let { put("gas_limit", it) }
            options.gasPrice?.let { put("gas_price", it) }
            options.nonce?.let { put("nonce", it) }
        }
        return post("/contract/send", body)
    }

    // ==================== Events ====================

    suspend fun getEvents(filter: EventFilter): List<DecodedEvent> {
        val body = buildJsonObject {
            put("contract", filter.contract)
            filter.event?.let { put("event", it) }
            filter.fromBlock?.let { put("from_block", it) }
            filter.toBlock?.let { put("to_block", it) }
            filter.topics?.let { put("topics", json.encodeToJsonElement(it)) }
            filter.abi?.let { put("abi", json.encodeToJsonElement(it)) }
        }
        return post("/contract/events", body)
    }

    suspend fun getLogs(contract: String, fromBlock: Long? = null, toBlock: Long? = null): List<EventLog> {
        var path = "/contract/logs?contract=${encode(contract)}"
        fromBlock?.let { path += "&from_block=$it" }
        toBlock?.let { path += "&to_block=$it" }
        return get(path)
    }

    suspend fun decodeLogs(logs: List<EventLog>, abi: List<AbiEntry>): List<DecodedEvent> {
        val body = buildJsonObject {
            put("logs", json.encodeToJsonElement(logs))
            put("abi", json.encodeToJsonElement(abi))
        }
        return post("/contract/decode-logs", body)
    }

    // ==================== ABI Utilities ====================

    suspend fun encodeCall(options: EncodeCallOptions): String {
        val body = buildJsonObject {
            put("method", options.method)
            put("args", json.encodeToJsonElement(options.args))
            put("abi", json.encodeToJsonElement(options.abi))
        }
        val response: JsonObject = post("/contract/encode", body)
        return response["data"]?.jsonPrimitive?.content ?: throw ContractException("Missing data")
    }

    suspend fun decodeResult(options: DecodeResultOptions): JsonElement {
        val body = buildJsonObject {
            put("data", options.data)
            put("method", options.method)
            put("abi", json.encodeToJsonElement(options.abi))
        }
        val response: JsonObject = post("/contract/decode", body)
        return response["result"] ?: throw ContractException("Missing result")
    }

    suspend fun getSelector(signature: String): String {
        val response: JsonObject = get("/contract/selector?signature=${encode(signature)}")
        return response["selector"]?.jsonPrimitive?.content ?: throw ContractException("Missing selector")
    }

    // ==================== Gas Estimation ====================

    suspend fun estimateGas(options: EstimateGasOptions): GasEstimation {
        val body = buildJsonObject {
            put("contract", options.contract)
            put("method", options.method)
            put("args", json.encodeToJsonElement(options.args))
            put("abi", json.encodeToJsonElement(options.abi))
            options.value?.let { put("value", it) }
        }
        return post("/contract/estimate-gas", body)
    }

    // ==================== Contract Information ====================

    suspend fun getBytecode(address: String): BytecodeInfo {
        return get("/contract/${encode(address)}/bytecode")
    }

    suspend fun verify(options: VerifyContractOptions): VerificationResult {
        val body = buildJsonObject {
            put("address", options.address)
            put("source_code", options.sourceCode)
            put("compiler_version", options.compilerVersion)
            options.constructorArgs?.let { put("constructor_args", it) }
            options.optimization?.let { put("optimization", it) }
            options.optimizationRuns?.let { put("optimization_runs", it) }
            options.license?.let { put("license", it) }
        }
        return post("/contract/verify", body)
    }

    suspend fun getVerificationStatus(address: String): VerificationResult {
        return get("/contract/${encode(address)}/verification")
    }

    // ==================== Multicall ====================

    suspend fun multicall(requests: List<MulticallRequest>): List<MulticallResult> {
        val body = buildJsonObject {
            put("calls", json.encodeToJsonElement(requests))
        }
        return post("/contract/multicall", body)
    }

    // ==================== Storage ====================

    suspend fun readStorage(options: ReadStorageOptions): String {
        var path = "/contract/storage?contract=${encode(options.contract)}&slot=${encode(options.slot)}"
        options.blockNumber?.let { path += "&block=$it" }
        val response: JsonObject = get(path)
        return response["value"]?.jsonPrimitive?.content ?: throw ContractException("Missing value")
    }

    // ==================== Lifecycle ====================

    suspend fun healthCheck(): Boolean {
        if (closed.get()) return false
        return try {
            val response: JsonObject = get("/health")
            response["status"]?.jsonPrimitive?.content == "healthy"
        } catch (e: Exception) {
            false
        }
    }

    override fun close() {
        closed.set(true)
        client.close()
    }

    fun isClosed(): Boolean = closed.get()

    // ==================== HTTP Helpers ====================

    private fun encode(value: String): String = URLEncoder.encode(value, "UTF-8")

    private suspend inline fun <reified T> get(path: String): T {
        checkClosed()
        return executeWithRetry { client.get("${config.endpoint}$path").body() }
    }

    private suspend inline fun <reified T> post(path: String, body: JsonObject): T {
        checkClosed()
        return executeWithRetry {
            client.post("${config.endpoint}$path") {
                setBody(body)
            }.body()
        }
    }

    private suspend inline fun <T> executeWithRetry(crossinline block: suspend () -> T): T {
        var lastException: Exception? = null
        repeat(config.retries) { attempt ->
            try {
                return block()
            } catch (e: Exception) {
                lastException = e
                if (attempt < config.retries - 1) {
                    delay((1L shl attempt) * 1000)
                }
            }
        }
        throw lastException ?: ContractException("Request failed")
    }

    private fun checkClosed() {
        if (closed.get()) throw ContractException("Client has been closed")
    }

    companion object {
        const val VERSION = "0.1.0"
    }
}

/**
 * Contract SDK configuration.
 */
data class ContractConfig(
    val apiKey: String,
    val endpoint: String = "https://contract.synor.io",
    val timeoutMs: Long = 30000,
    val retries: Int = 3
)

/**
 * Contract SDK exception.
 */
class ContractException(
    message: String,
    val statusCode: Int? = null,
    val errorCode: String? = null
) : RuntimeException(message)
