package io.synor.bridge

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.delay
import java.util.concurrent.atomic.AtomicBoolean

/**
 * Synor Bridge SDK for Kotlin.
 * Cross-chain asset transfers with lock-mint and burn-unlock patterns.
 */
class SynorBridge(private val config: BridgeConfig) : AutoCloseable {

    companion object {
        private val gson: Gson = GsonBuilder().create()
        private val FINAL_STATUSES = setOf(TransferStatus.completed, TransferStatus.failed, TransferStatus.refunded)
    }

    private val httpClient = HttpClient(CIO) {
        engine { requestTimeout = config.timeoutSecs * 1000 }
    }
    private val closed = AtomicBoolean(false)

    constructor(apiKey: String) : this(BridgeConfig(apiKey))

    // ==================== Chain Operations ====================

    suspend fun getSupportedChains(): List<Chain> {
        val resp = request("GET", "/chains")
        val result = gson.fromJson<ChainsResponse>(resp, ChainsResponse::class.java)
        return result.chains ?: emptyList()
    }

    suspend fun getChain(chainId: ChainId): Chain {
        val resp = request("GET", "/chains/${chainId.name.lowercase()}")
        return gson.fromJson(resp, Chain::class.java)
    }

    suspend fun isChainSupported(chainId: ChainId): Boolean = try {
        getChain(chainId).supported
    } catch (e: Exception) { false }

    // ==================== Asset Operations ====================

    suspend fun getSupportedAssets(chainId: ChainId): List<Asset> {
        val resp = request("GET", "/chains/${chainId.name.lowercase()}/assets")
        val result = gson.fromJson<AssetsResponse>(resp, AssetsResponse::class.java)
        return result.assets ?: emptyList()
    }

    suspend fun getAsset(assetId: String): Asset {
        val resp = request("GET", "/assets/${assetId.urlEncode()}")
        return gson.fromJson(resp, Asset::class.java)
    }

    suspend fun getWrappedAsset(originalAssetId: String, targetChain: ChainId): WrappedAsset {
        val path = "/assets/${originalAssetId.urlEncode()}/wrapped/${targetChain.name.lowercase()}"
        val resp = request("GET", path)
        return gson.fromJson(resp, WrappedAsset::class.java)
    }

    // ==================== Fee & Rate Operations ====================

    suspend fun estimateFee(asset: String, amount: String, sourceChain: ChainId, targetChain: ChainId): FeeEstimate {
        val body = mapOf(
            "asset" to asset,
            "amount" to amount,
            "sourceChain" to sourceChain.name.lowercase(),
            "targetChain" to targetChain.name.lowercase()
        )
        val resp = request("POST", "/fees/estimate", body)
        return gson.fromJson(resp, FeeEstimate::class.java)
    }

    suspend fun getExchangeRate(fromAsset: String, toAsset: String): ExchangeRate {
        val resp = request("GET", "/rates/${fromAsset.urlEncode()}/${toAsset.urlEncode()}")
        return gson.fromJson(resp, ExchangeRate::class.java)
    }

    // ==================== Lock-Mint Flow ====================

    suspend fun lock(asset: String, amount: String, targetChain: ChainId, options: LockOptions? = null): LockReceipt {
        val body = mutableMapOf<String, Any>(
            "asset" to asset,
            "amount" to amount,
            "targetChain" to targetChain.name.lowercase()
        )
        options?.recipient?.let { body["recipient"] = it }
        options?.deadline?.let { body["deadline"] = it }
        options?.slippage?.let { body["slippage"] = it }
        val resp = request("POST", "/transfers/lock", body)
        return gson.fromJson(resp, LockReceipt::class.java)
    }

    suspend fun getLockProof(lockReceiptId: String): LockProof {
        val resp = request("GET", "/transfers/lock/${lockReceiptId.urlEncode()}/proof")
        return gson.fromJson(resp, LockProof::class.java)
    }

    suspend fun waitForLockProof(lockReceiptId: String, pollIntervalMs: Long = 5000, maxWaitMs: Long = 600000): LockProof {
        val deadline = System.currentTimeMillis() + maxWaitMs
        while (System.currentTimeMillis() < deadline) {
            try {
                return getLockProof(lockReceiptId)
            } catch (e: BridgeException) {
                if (e.code == "CONFIRMATIONS_PENDING") {
                    delay(pollIntervalMs)
                    continue
                }
                throw e
            }
        }
        throw BridgeException("Timeout waiting for lock proof", "CONFIRMATIONS_PENDING")
    }

    suspend fun mint(proof: LockProof, targetAddress: String, options: MintOptions? = null): SignedTransaction {
        val body = mutableMapOf<String, Any>("proof" to proof, "targetAddress" to targetAddress)
        options?.gasLimit?.let { body["gasLimit"] = it }
        options?.maxFeePerGas?.let { body["maxFeePerGas"] = it }
        options?.maxPriorityFeePerGas?.let { body["maxPriorityFeePerGas"] = it }
        val resp = request("POST", "/transfers/mint", body)
        return gson.fromJson(resp, SignedTransaction::class.java)
    }

    // ==================== Burn-Unlock Flow ====================

    suspend fun burn(wrappedAsset: String, amount: String, options: BurnOptions? = null): BurnReceipt {
        val body = mutableMapOf<String, Any>("wrappedAsset" to wrappedAsset, "amount" to amount)
        options?.recipient?.let { body["recipient"] = it }
        options?.deadline?.let { body["deadline"] = it }
        val resp = request("POST", "/transfers/burn", body)
        return gson.fromJson(resp, BurnReceipt::class.java)
    }

    suspend fun getBurnProof(burnReceiptId: String): BurnProof {
        val resp = request("GET", "/transfers/burn/${burnReceiptId.urlEncode()}/proof")
        return gson.fromJson(resp, BurnProof::class.java)
    }

    suspend fun waitForBurnProof(burnReceiptId: String, pollIntervalMs: Long = 5000, maxWaitMs: Long = 600000): BurnProof {
        val deadline = System.currentTimeMillis() + maxWaitMs
        while (System.currentTimeMillis() < deadline) {
            try {
                return getBurnProof(burnReceiptId)
            } catch (e: BridgeException) {
                if (e.code == "CONFIRMATIONS_PENDING") {
                    delay(pollIntervalMs)
                    continue
                }
                throw e
            }
        }
        throw BridgeException("Timeout waiting for burn proof", "CONFIRMATIONS_PENDING")
    }

    suspend fun unlock(proof: BurnProof, options: UnlockOptions? = null): SignedTransaction {
        val body = mutableMapOf<String, Any>("proof" to proof)
        options?.gasLimit?.let { body["gasLimit"] = it }
        options?.gasPrice?.let { body["gasPrice"] = it }
        val resp = request("POST", "/transfers/unlock", body)
        return gson.fromJson(resp, SignedTransaction::class.java)
    }

    // ==================== Transfer Management ====================

    suspend fun getTransfer(transferId: String): Transfer {
        val resp = request("GET", "/transfers/${transferId.urlEncode()}")
        return gson.fromJson(resp, Transfer::class.java)
    }

    suspend fun getTransferStatus(transferId: String): TransferStatus = getTransfer(transferId).status

    suspend fun listTransfers(filter: TransferFilter? = null): List<Transfer> {
        val params = mutableListOf<String>()
        filter?.status?.let { params.add("status=${it.name.lowercase()}") }
        filter?.sourceChain?.let { params.add("sourceChain=${it.name.lowercase()}") }
        filter?.targetChain?.let { params.add("targetChain=${it.name.lowercase()}") }
        filter?.limit?.let { params.add("limit=$it") }
        filter?.offset?.let { params.add("offset=$it") }

        val path = if (params.isNotEmpty()) "/transfers?${params.joinToString("&")}" else "/transfers"
        val resp = request("GET", path)
        val result = gson.fromJson<TransfersResponse>(resp, TransfersResponse::class.java)
        return result.transfers ?: emptyList()
    }

    suspend fun waitForTransfer(transferId: String, pollIntervalMs: Long = 10000, maxWaitMs: Long = 1800000): Transfer {
        val deadline = System.currentTimeMillis() + maxWaitMs
        while (System.currentTimeMillis() < deadline) {
            val transfer = getTransfer(transferId)
            if (transfer.status in FINAL_STATUSES) return transfer
            delay(pollIntervalMs)
        }
        throw BridgeException("Timeout waiting for transfer completion")
    }

    // ==================== Convenience Methods ====================

    suspend fun bridgeTo(
        asset: String,
        amount: String,
        targetChain: ChainId,
        targetAddress: String,
        lockOptions: LockOptions? = null,
        mintOptions: MintOptions? = null
    ): Transfer {
        val lockReceipt = lock(asset, amount, targetChain, lockOptions)
        if (config.debug) println("Locked: ${lockReceipt.id}, waiting for confirmations...")

        val proof = waitForLockProof(lockReceipt.id)
        if (config.debug) println("Proof ready, minting on ${targetChain.name}...")

        mint(proof, targetAddress, mintOptions)
        return waitForTransfer(lockReceipt.id)
    }

    suspend fun bridgeBack(
        wrappedAsset: String,
        amount: String,
        burnOptions: BurnOptions? = null,
        unlockOptions: UnlockOptions? = null
    ): Transfer {
        val burnReceipt = burn(wrappedAsset, amount, burnOptions)
        if (config.debug) println("Burned: ${burnReceipt.id}, waiting for confirmations...")

        val proof = waitForBurnProof(burnReceipt.id)
        if (config.debug) println("Proof ready, unlocking on ${burnReceipt.targetChain}...")

        unlock(proof, unlockOptions)
        return waitForTransfer(burnReceipt.id)
    }

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

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

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

    suspend fun healthCheck(): Boolean = try {
        val resp = request("GET", "/health")
        val map = gson.fromJson<Map<String, Any>>(resp, object : TypeToken<Map<String, Any>>() {}.type)
        map["status"] == "healthy"
    } catch (e: Exception) { false }

    // ==================== Private Methods ====================

    private suspend fun request(method: String, path: String, body: Any? = null): String {
        if (closed.get()) throw BridgeException("Client has been closed")

        var lastError: Exception? = null
        repeat(config.retries) { attempt ->
            try {
                return doRequest(method, path, body)
            } catch (e: Exception) {
                lastError = e
                if (attempt < config.retries - 1) delay((1L shl attempt) * 1000)
            }
        }
        throw lastError ?: BridgeException("Unknown error")
    }

    private suspend fun doRequest(method: String, path: String, body: Any?): String {
        val url = "${config.endpoint}$path"
        val response: HttpResponse = httpClient.request(url) {
            this.method = HttpMethod.parse(method)
            header("Authorization", "Bearer ${config.apiKey}")
            header("Content-Type", "application/json")
            header("X-SDK-Version", "kotlin/0.1.0")
            body?.let { setBody(gson.toJson(it)) }
        }

        val responseBody = response.bodyAsText()
        if (response.status.value >= 400) {
            val errorMap = try {
                gson.fromJson<Map<String, Any>>(responseBody, object : TypeToken<Map<String, Any>>() {}.type)
            } catch (e: Exception) { emptyMap() }
            throw BridgeException(
                errorMap["message"] as? String ?: "HTTP ${response.status.value}",
                errorMap["code"] as? String,
                response.status.value
            )
        }
        return responseBody
    }

    private fun String.urlEncode(): String = java.net.URLEncoder.encode(this, "UTF-8")
}

// ==================== Types ====================

data class BridgeConfig(
    val apiKey: String,
    val endpoint: String = "https://bridge.synor.io/v1",
    val timeoutSecs: Long = 60,
    val retries: Int = 3,
    val debug: Boolean = false
)

enum class ChainId { synor, ethereum, polygon, arbitrum, optimism, bsc, avalanche, solana, cosmos }
enum class AssetType { native, erc20, erc721, erc1155 }
enum class TransferStatus { pending, locked, confirming, minting, completed, failed, refunded }
enum class TransferDirection { lock_mint, burn_unlock }

data class NativeCurrency(val name: String, val symbol: String, val decimals: Int)

data class Chain(
    val id: ChainId,
    val name: String,
    val chainId: Long,
    val rpcUrl: String,
    val explorerUrl: String,
    val nativeCurrency: NativeCurrency,
    val confirmations: Int,
    val estimatedBlockTime: Int,
    val supported: Boolean
)

data class Asset(
    val id: String,
    val symbol: String,
    val name: String,
    val type: AssetType,
    val chain: ChainId,
    val contractAddress: String? = null,
    val decimals: Int,
    val logoUrl: String? = null,
    val verified: Boolean = false
)

data class WrappedAsset(
    val originalAsset: Asset,
    val wrappedAsset: Asset,
    val chain: ChainId,
    val bridgeContract: String
)

data class ValidatorSignature(val validator: String, val signature: String, val timestamp: Long)

data class LockReceipt(
    val id: String,
    val txHash: String,
    val sourceChain: ChainId,
    val targetChain: ChainId,
    val asset: Asset,
    val amount: String,
    val sender: String,
    val recipient: String,
    val lockTimestamp: Long,
    val confirmations: Int,
    val requiredConfirmations: Int
)

data class LockProof(
    val lockReceipt: LockReceipt,
    val merkleProof: List<String>,
    val blockHeader: String,
    val signatures: List<ValidatorSignature>
)

data class BurnReceipt(
    val id: String,
    val txHash: String,
    val sourceChain: ChainId,
    val targetChain: ChainId,
    val wrappedAsset: Asset,
    val originalAsset: Asset,
    val amount: String,
    val sender: String,
    val recipient: String,
    val burnTimestamp: Long,
    val confirmations: Int,
    val requiredConfirmations: Int
)

data class BurnProof(
    val burnReceipt: BurnReceipt,
    val merkleProof: List<String>,
    val blockHeader: String,
    val signatures: List<ValidatorSignature>
)

data class Transfer(
    val id: String,
    val direction: TransferDirection,
    val status: TransferStatus,
    val sourceChain: ChainId,
    val targetChain: ChainId,
    val asset: Asset,
    val amount: String,
    val sender: String,
    val recipient: String,
    val sourceTxHash: String? = null,
    val targetTxHash: String? = null,
    val fee: String,
    val feeAsset: Asset,
    val createdAt: Long,
    val updatedAt: Long,
    val completedAt: Long? = null,
    val errorMessage: String? = null
)

data class FeeEstimate(
    val bridgeFee: String,
    val gasFeeSource: String,
    val gasFeeTarget: String,
    val totalFee: String,
    val feeAsset: Asset,
    val estimatedTime: Int,
    val exchangeRate: String? = null
)

data class ExchangeRate(
    val fromAsset: Asset,
    val toAsset: Asset,
    val rate: String,
    val inverseRate: String,
    val lastUpdated: Long,
    val source: String
)

data class SignedTransaction(
    val txHash: String,
    val chain: ChainId,
    val from: String,
    val to: String,
    val value: String,
    val data: String,
    val gasLimit: String,
    val gasPrice: String? = null,
    val maxFeePerGas: String? = null,
    val maxPriorityFeePerGas: String? = null,
    val nonce: Int,
    val signature: String
)

data class LockOptions(val recipient: String? = null, val deadline: Long? = null, val slippage: Double? = null)
data class MintOptions(val gasLimit: String? = null, val maxFeePerGas: String? = null, val maxPriorityFeePerGas: String? = null)
data class BurnOptions(val recipient: String? = null, val deadline: Long? = null)
data class UnlockOptions(val gasLimit: String? = null, val gasPrice: String? = null)

data class TransferFilter(
    val status: TransferStatus? = null,
    val sourceChain: ChainId? = null,
    val targetChain: ChainId? = null,
    val asset: String? = null,
    val sender: String? = null,
    val recipient: String? = null,
    val limit: Int? = null,
    val offset: Int? = null
)

internal data class ChainsResponse(val chains: List<Chain>?)
internal data class AssetsResponse(val assets: List<Asset>?)
internal data class TransfersResponse(val transfers: List<Transfer>?)

class BridgeException(
    message: String,
    val code: String? = null,
    val statusCode: Int = 0
) : RuntimeException(message)
