package io.synor.wallet

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.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
import java.io.Closeable

/**
 * Synor Wallet SDK client for Kotlin.
 *
 * Provides key management, transaction signing, and balance queries
 * for the Synor blockchain.
 *
 * Example:
 * ```kotlin
 * val wallet = SynorWallet(WalletConfig.builder("your-api-key").build())
 *
 * // Create a new wallet
 * val result = wallet.createWallet(WalletType.STANDARD)
 * println("Wallet ID: ${result.wallet.id}")
 * println("Mnemonic: ${result.mnemonic}")
 *
 * // Get balance
 * val balance = wallet.getBalance(result.wallet.addresses[0].address)
 * println("Balance: ${balance.total}")
 *
 * wallet.close()
 * ```
 */
class SynorWallet(private val config: WalletConfig) : Closeable {

    private val json = Json {
        ignoreUnknownKeys = true
        isLenient = true
        encodeDefaults = true
    }

    private val client = HttpClient(CIO) {
        install(ContentNegotiation) {
            json(json)
        }

        install(HttpTimeout) {
            requestTimeoutMillis = config.timeout
            connectTimeoutMillis = config.timeout
        }

        defaultRequest {
            header("Authorization", "Bearer ${config.apiKey}")
            header("Content-Type", "application/json")
        }
    }

    /**
     * Create a new wallet.
     *
     * @param type The type of wallet to create
     * @return Result containing the wallet and optionally the mnemonic
     */
    suspend fun createWallet(type: WalletType = WalletType.STANDARD): CreateWalletResult {
        val request = CreateWalletRequest(type, config.network)
        return post("/wallets", request)
    }

    /**
     * Import a wallet from a mnemonic phrase.
     *
     * @param mnemonic The 12 or 24 word mnemonic phrase
     * @param passphrase Optional BIP39 passphrase
     * @return The imported wallet
     */
    suspend fun importWallet(mnemonic: String, passphrase: String? = null): Wallet {
        val request = ImportWalletRequest(mnemonic, passphrase, config.network)
        return post("/wallets/import", request)
    }

    /**
     * Get a wallet by ID.
     *
     * @param walletId The wallet ID
     * @return The wallet
     */
    suspend fun getWallet(walletId: String): Wallet {
        return get("/wallets/$walletId")
    }

    /**
     * Generate a new address for a wallet.
     *
     * @param walletId The wallet ID
     * @param isChange Whether this is a change address
     * @return The new address
     */
    suspend fun generateAddress(walletId: String, isChange: Boolean = false): Address {
        return post("/wallets/$walletId/addresses", mapOf("is_change" to isChange))
    }

    /**
     * Get a stealth address for privacy transactions.
     *
     * @param walletId The wallet ID
     * @return The stealth address
     */
    suspend fun getStealthAddress(walletId: String): StealthAddress {
        return get("/wallets/$walletId/stealth-address")
    }

    /**
     * Sign a transaction.
     *
     * @param walletId The wallet ID to sign with
     * @param transaction The transaction to sign
     * @return The signed transaction
     */
    suspend fun signTransaction(walletId: String, transaction: Transaction): SignedTransaction {
        val request = SignTransactionRequest(walletId, transaction)
        return post("/transactions/sign", request)
    }

    /**
     * Sign a message with a wallet address.
     *
     * @param walletId The wallet ID
     * @param message The message to sign
     * @param addressIndex The address index to use for signing
     * @return The signature
     */
    suspend fun signMessage(walletId: String, message: String, addressIndex: Int = 0): Signature {
        val request = SignMessageRequest(walletId, message, addressIndex)
        return post("/messages/sign", request)
    }

    /**
     * Verify a message signature.
     *
     * @param message The original message
     * @param signature The signature to verify
     * @param address The address that signed the message
     * @return True if the signature is valid
     */
    suspend fun verifyMessage(message: String, signature: String, address: String): Boolean {
        val request = mapOf(
            "message" to message,
            "signature" to signature,
            "address" to address
        )
        val response: Map<String, Boolean> = post("/messages/verify", request)
        return response["valid"] ?: false
    }

    /**
     * Get the balance for an address.
     *
     * @param address The address to query
     * @return The balance information
     */
    suspend fun getBalance(address: String): Balance {
        return get("/addresses/$address/balance")
    }

    /**
     * Get UTXOs for an address.
     *
     * @param address The address to query
     * @param minConfirmations Minimum confirmations required
     * @return List of UTXOs
     */
    suspend fun getUTXOs(address: String, minConfirmations: Int = 1): List<UTXO> {
        return get("/addresses/$address/utxos?min_confirmations=$minConfirmations")
    }

    /**
     * Estimate transaction fee.
     *
     * @param priority The transaction priority
     * @return Estimated fee in satoshis per byte
     */
    suspend fun estimateFee(priority: Priority = Priority.MEDIUM): Long {
        val response: Map<String, Long> = get("/fees/estimate?priority=${priority.name.lowercase()}")
        return response["fee_per_byte"] ?: 0L
    }

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

    private suspend inline fun <reified T, reified R> post(path: String, body: R): T {
        return executeWithRetry {
            val response = client.post("${config.endpoint}$path") {
                setBody(body)
            }
            handleResponse(response)
        }
    }

    private suspend inline fun <reified T> handleResponse(response: HttpResponse): T {
        if (response.status.isSuccess()) {
            return response.body()
        }

        val errorBody = try {
            response.bodyAsText()
        } catch (e: Exception) {
            "Unknown error"
        }

        throw WalletException(
            message = "Wallet API error: $errorBody",
            statusCode = response.status.value
        )
    }

    private suspend inline fun <T> executeWithRetry(block: () -> T): T {
        var lastException: Exception? = null

        repeat(config.retries) { attempt ->
            try {
                return block()
            } catch (e: Exception) {
                lastException = e
                if (config.debug) {
                    println("Attempt ${attempt + 1} failed: ${e.message}")
                }
                if (attempt < config.retries - 1) {
                    kotlinx.coroutines.delay(1000L * (attempt + 1))
                }
            }
        }

        throw lastException ?: WalletException("Unknown error after ${config.retries} retries")
    }

    override fun close() {
        client.close()
    }
}
