package io.synor.rpc

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
 * Network environment.
 */
@Serializable
enum class Network {
    @SerialName("mainnet") MAINNET,
    @SerialName("testnet") TESTNET,
    @SerialName("devnet") DEVNET
}

/**
 * Transaction priority for fee estimation.
 */
@Serializable
enum class Priority {
    @SerialName("low") LOW,
    @SerialName("medium") MEDIUM,
    @SerialName("high") HIGH
}

/**
 * Transaction status.
 */
@Serializable
enum class TransactionStatus {
    @SerialName("pending") PENDING,
    @SerialName("confirmed") CONFIRMED,
    @SerialName("failed") FAILED
}

/**
 * Configuration for the RPC client.
 */
data class RpcConfig(
    val apiKey: String,
    val endpoint: String = "https://rpc.synor.io/v1",
    val wsEndpoint: String = "wss://rpc.synor.io/v1/ws",
    val network: Network = Network.MAINNET,
    val timeout: Long = 30_000L,
    val retries: Int = 3,
    val debug: Boolean = false
) {
    class Builder(private val apiKey: String) {
        private var endpoint: String = "https://rpc.synor.io/v1"
        private var wsEndpoint: String = "wss://rpc.synor.io/v1/ws"
        private var network: Network = Network.MAINNET
        private var timeout: Long = 30_000L
        private var retries: Int = 3
        private var debug: Boolean = false

        fun endpoint(endpoint: String) = apply { this.endpoint = endpoint }
        fun wsEndpoint(wsEndpoint: String) = apply { this.wsEndpoint = wsEndpoint }
        fun network(network: Network) = apply { this.network = network }
        fun timeout(timeout: Long) = apply { this.timeout = timeout }
        fun retries(retries: Int) = apply { this.retries = retries }
        fun debug(debug: Boolean) = apply { this.debug = debug }

        fun build() = RpcConfig(apiKey, endpoint, wsEndpoint, network, timeout, retries, debug)
    }

    companion object {
        fun builder(apiKey: String) = Builder(apiKey)
    }
}

/**
 * Block header information.
 */
@Serializable
data class BlockHeader(
    val hash: String,
    val height: Long,
    @SerialName("previous_hash") val previousHash: String,
    val timestamp: Long,
    val version: Int,
    @SerialName("merkle_root") val merkleRoot: String,
    val nonce: Long,
    val difficulty: Double
)

/**
 * A blockchain block.
 */
@Serializable
data class Block(
    val hash: String,
    val height: Long,
    @SerialName("previous_hash") val previousHash: String,
    val timestamp: Long,
    val version: Int,
    @SerialName("merkle_root") val merkleRoot: String,
    val nonce: Long,
    val difficulty: Double,
    val transactions: List<Transaction>,
    val size: Int,
    val weight: Int
)

/**
 * A blockchain transaction.
 */
@Serializable
data class Transaction(
    val txid: String,
    val hash: String,
    val version: Int,
    val size: Int,
    val weight: Int,
    @SerialName("lock_time") val lockTime: Long,
    val inputs: List<TransactionInput>,
    val outputs: List<TransactionOutput>,
    val fee: Long,
    val confirmations: Int,
    @SerialName("block_hash") val blockHash: String? = null,
    @SerialName("block_height") val blockHeight: Long? = null,
    val timestamp: Long? = null,
    val status: TransactionStatus = TransactionStatus.PENDING
)

/**
 * Transaction input.
 */
@Serializable
data class TransactionInput(
    val txid: String,
    val vout: Int,
    @SerialName("script_sig") val scriptSig: String? = null,
    val sequence: Long,
    val witness: List<String>? = null
)

/**
 * Transaction output.
 */
@Serializable
data class TransactionOutput(
    val value: Long,
    val n: Int,
    @SerialName("script_pubkey") val scriptPubKey: ScriptPubKey
)

/**
 * Script public key.
 */
@Serializable
data class ScriptPubKey(
    val asm: String,
    val hex: String,
    val type: String,
    val address: String? = null
)

/**
 * Chain information.
 */
@Serializable
data class ChainInfo(
    val chain: String,
    val blocks: Long,
    val headers: Long,
    @SerialName("best_block_hash") val bestBlockHash: String,
    val difficulty: Double,
    @SerialName("median_time") val medianTime: Long,
    @SerialName("verification_progress") val verificationProgress: Double,
    @SerialName("chain_work") val chainWork: String,
    val pruned: Boolean
)

/**
 * Mempool information.
 */
@Serializable
data class MempoolInfo(
    val size: Int,
    val bytes: Long,
    val usage: Long,
    @SerialName("max_mempool") val maxMempool: Long,
    @SerialName("mempool_min_fee") val mempoolMinFee: Double,
    @SerialName("min_relay_tx_fee") val minRelayTxFee: Double
)

/**
 * Fee estimate for transactions.
 */
@Serializable
data class FeeEstimate(
    val priority: Priority,
    @SerialName("fee_rate") val feeRate: Long,
    @SerialName("estimated_blocks") val estimatedBlocks: Int
)

/**
 * Result of transaction submission.
 */
@Serializable
data class SubmitResult(
    val txid: String,
    val accepted: Boolean,
    val reason: String? = null
)

/**
 * Subscription handle for WebSocket events.
 */
data class Subscription(
    val id: String,
    val channel: String,
    private val onCancel: () -> Unit
) {
    fun cancel() = onCancel()
}

/**
 * WebSocket message wrapper.
 */
@Serializable
internal data class WsMessage(
    val type: String,
    val channel: String,
    val data: String? = null,
    val filter: String? = null
)

/**
 * Exception thrown by RPC operations.
 */
class RpcException(
    message: String,
    val code: String? = null,
    val statusCode: Int? = null,
    cause: Throwable? = null
) : Exception(message, cause)
