package io.synor.dex

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.*
import kotlinx.serialization.json.Json
import java.math.BigInteger
import java.time.Instant
import java.util.concurrent.atomic.AtomicBoolean

/**
 * Synor DEX SDK Client
 *
 * Complete decentralized exchange client with support for:
 * - AMM swaps (constant product, stable, concentrated)
 * - Liquidity provision
 * - Perpetual futures (up to 100x leverage)
 * - Order books (limit orders)
 * - Farming & staking
 */
class SynorDex(private val config: DexConfig) : AutoCloseable {
    private val client = HttpClient(CIO) {
        install(ContentNegotiation) {
            json(Json {
                ignoreUnknownKeys = true
                isLenient = true
            })
        }
        install(HttpTimeout) {
            requestTimeoutMillis = config.timeout
        }
        defaultRequest {
            header("Content-Type", "application/json")
            header("Authorization", "Bearer ${config.apiKey}")
            header("X-SDK-Version", "kotlin/0.1.0")
        }
    }

    private val closed = AtomicBoolean(false)

    val perps = PerpsClient(this)
    val orderbook = OrderBookClient(this)
    val farms = FarmsClient(this)

    // Token Operations

    suspend fun getToken(address: String): Token =
        get("/tokens/$address")

    suspend fun listTokens(): List<Token> =
        get("/tokens")

    suspend fun searchTokens(query: String): List<Token> =
        get("/tokens/search?q=${query.encodeURLParameter()}")

    // Pool Operations

    suspend fun getPool(tokenA: String, tokenB: String): Pool =
        get("/pools/$tokenA/$tokenB")

    suspend fun getPoolById(poolId: String): Pool =
        get("/pools/$poolId")

    suspend fun listPools(filter: PoolFilter? = null): List<Pool> {
        val params = mutableListOf<String>()
        filter?.let { f ->
            f.tokens?.let { params.add("tokens=${it.joinToString(",")}") }
            f.minTvl?.let { params.add("min_tvl=$it") }
            f.minVolume24h?.let { params.add("min_volume=$it") }
            f.verified?.let { params.add("verified=$it") }
            f.limit?.let { params.add("limit=$it") }
            f.offset?.let { params.add("offset=$it") }
        }
        val path = if (params.isEmpty()) "/pools" else "/pools?${params.joinToString("&")}"
        return get(path)
    }

    // Swap Operations

    suspend fun getQuote(params: QuoteParams): Quote =
        post("/swap/quote", mapOf(
            "token_in" to params.tokenIn,
            "token_out" to params.tokenOut,
            "amount_in" to params.amountIn.toString(),
            "slippage" to (params.slippage ?: 0.005)
        ))

    suspend fun swap(params: SwapParams): SwapResult {
        val deadline = params.deadline ?: (Instant.now().epochSecond + 1200)
        return post("/swap", buildMap {
            put("token_in", params.tokenIn)
            put("token_out", params.tokenOut)
            put("amount_in", params.amountIn.toString())
            put("min_amount_out", params.minAmountOut.toString())
            put("deadline", deadline)
            params.recipient?.let { put("recipient", it) }
        })
    }

    // Liquidity Operations

    suspend fun addLiquidity(params: AddLiquidityParams): LiquidityResult {
        val deadline = params.deadline ?: (Instant.now().epochSecond + 1200)
        return post("/liquidity/add", buildMap {
            put("token_a", params.tokenA)
            put("token_b", params.tokenB)
            put("amount_a", params.amountA.toString())
            put("amount_b", params.amountB.toString())
            put("deadline", deadline)
            params.minAmountA?.let { put("min_amount_a", it.toString()) }
            params.minAmountB?.let { put("min_amount_b", it.toString()) }
        })
    }

    suspend fun removeLiquidity(params: RemoveLiquidityParams): LiquidityResult {
        val deadline = params.deadline ?: (Instant.now().epochSecond + 1200)
        return post("/liquidity/remove", buildMap {
            put("pool", params.pool)
            put("lp_amount", params.lpAmount.toString())
            put("deadline", deadline)
            params.minAmountA?.let { put("min_amount_a", it.toString()) }
            params.minAmountB?.let { put("min_amount_b", it.toString()) }
        })
    }

    suspend fun getMyPositions(): List<LPPosition> =
        get("/liquidity/positions")

    // Analytics

    suspend fun getPriceHistory(pair: String, interval: String, limit: Int = 100): List<OHLCV> =
        get("/analytics/candles/$pair?interval=$interval&limit=$limit")

    suspend fun getTradeHistory(pair: String, limit: Int = 50): List<TradeHistory> =
        get("/analytics/trades/$pair?limit=$limit")

    suspend fun getVolumeStats(): VolumeStats =
        get("/analytics/volume")

    suspend fun getTVL(): TVLStats =
        get("/analytics/tvl")

    // Lifecycle

    suspend fun healthCheck(): Boolean = try {
        val response: HealthResponse = get("/health")
        response.status == "healthy"
    } catch (e: Exception) {
        false
    }

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

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

    // Internal methods

    internal suspend inline fun <reified T> get(path: String): T {
        checkClosed()
        return client.get(config.endpoint + path).body()
    }

    internal suspend inline fun <reified T> post(path: String, body: Map<String, Any?>): T {
        checkClosed()
        return client.post(config.endpoint + path) {
            setBody(body)
        }.body()
    }

    internal suspend inline fun <reified T> delete(path: String): T {
        checkClosed()
        return client.delete(config.endpoint + path).body()
    }

    private fun checkClosed() {
        if (closed.get()) {
            throw DexException("Client has been closed", "CLIENT_CLOSED", 0)
        }
    }

    private data class HealthResponse(val status: String)
}

/**
 * DEX client configuration
 */
data class DexConfig(
    val apiKey: String,
    val endpoint: String = "https://dex.synor.io/v1",
    val wsEndpoint: String = "wss://dex.synor.io/v1/ws",
    val timeout: Long = 30000,
    val retries: Int = 3,
    val debug: Boolean = false
)

/**
 * DEX SDK Exception
 */
class DexException(
    message: String,
    val code: String?,
    val status: Int
) : RuntimeException(message)

/**
 * Perpetual futures sub-client
 */
class PerpsClient(private val dex: SynorDex) {
    suspend fun listMarkets(): List<PerpMarket> =
        dex.get("/perps/markets")

    suspend fun getMarket(symbol: String): PerpMarket =
        dex.get("/perps/markets/$symbol")

    suspend fun openPosition(params: OpenPositionParams): PerpPosition =
        dex.post("/perps/positions", buildMap {
            put("market", params.market)
            put("side", params.side.name.lowercase())
            put("size", params.size.toString())
            put("leverage", params.leverage)
            put("order_type", params.orderType.name.lowercase())
            put("margin_type", params.marginType.name.lowercase())
            put("reduce_only", params.reduceOnly)
            params.limitPrice?.let { put("limit_price", it) }
            params.stopLoss?.let { put("stop_loss", it) }
            params.takeProfit?.let { put("take_profit", it) }
        })

    suspend fun closePosition(params: ClosePositionParams): PerpPosition =
        dex.post("/perps/positions/close", buildMap {
            put("market", params.market)
            put("order_type", params.orderType.name.lowercase())
            params.size?.let { put("size", it.toString()) }
            params.limitPrice?.let { put("limit_price", it) }
        })

    suspend fun modifyPosition(params: ModifyPositionParams): PerpPosition =
        dex.post("/perps/positions/${params.positionId}/modify", buildMap {
            params.newLeverage?.let { put("new_leverage", it) }
            params.newMargin?.let { put("new_margin", it.toString()) }
            params.newStopLoss?.let { put("new_stop_loss", it) }
            params.newTakeProfit?.let { put("new_take_profit", it) }
        })

    suspend fun getPositions(): List<PerpPosition> =
        dex.get("/perps/positions")

    suspend fun getPosition(market: String): PerpPosition? =
        dex.get("/perps/positions/$market")

    suspend fun getOrders(): List<PerpOrder> =
        dex.get("/perps/orders")

    suspend fun cancelOrder(orderId: String) {
        dex.delete<Unit>("/perps/orders/$orderId")
    }

    suspend fun cancelAllOrders(market: String? = null): Int {
        val path = if (market != null) "/perps/orders?market=$market" else "/perps/orders"
        val result: CancelResult = dex.delete(path)
        return result.cancelled
    }

    suspend fun getFundingHistory(market: String, limit: Int = 100): List<FundingPayment> =
        dex.get("/perps/funding/$market?limit=$limit")

    suspend fun getFundingRate(market: String): FundingRateInfo =
        dex.get("/perps/funding/$market/current")

    private data class CancelResult(val cancelled: Int)
}

/**
 * Order book sub-client
 */
class OrderBookClient(private val dex: SynorDex) {
    suspend fun getOrderBook(market: String, depth: Int = 20): OrderBook =
        dex.get("/orderbook/$market?depth=$depth")

    suspend fun placeLimitOrder(params: LimitOrderParams): Order =
        dex.post("/orderbook/orders", mapOf(
            "market" to params.market,
            "side" to params.side,
            "price" to params.price,
            "size" to params.size.toString(),
            "time_in_force" to params.timeInForce.name,
            "post_only" to params.postOnly
        ))

    suspend fun cancelOrder(orderId: String) {
        dex.delete<Unit>("/orderbook/orders/$orderId")
    }

    suspend fun getOpenOrders(market: String? = null): List<Order> {
        val path = if (market != null) "/orderbook/orders?market=$market" else "/orderbook/orders"
        return dex.get(path)
    }

    suspend fun getOrderHistory(limit: Int = 50): List<Order> =
        dex.get("/orderbook/orders/history?limit=$limit")
}

/**
 * Farms sub-client
 */
class FarmsClient(private val dex: SynorDex) {
    suspend fun listFarms(): List<Farm> =
        dex.get("/farms")

    suspend fun getFarm(farmId: String): Farm =
        dex.get("/farms/$farmId")

    suspend fun stake(params: StakeParams): FarmPosition =
        dex.post("/farms/stake", mapOf(
            "farm" to params.farm,
            "amount" to params.amount.toString()
        ))

    suspend fun unstake(farm: String, amount: BigInteger): FarmPosition =
        dex.post("/farms/unstake", mapOf(
            "farm" to farm,
            "amount" to amount.toString()
        ))

    suspend fun claimRewards(farm: String): ClaimRewardsResult =
        dex.post("/farms/claim", mapOf("farm" to farm))

    suspend fun getMyFarmPositions(): List<FarmPosition> =
        dex.get("/farms/positions")
}

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