package io.synor.ibc

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.math.BigInteger

/**
 * Synor IBC SDK Types for Kotlin
 *
 * Inter-Blockchain Communication (IBC) protocol types.
 */

/**
 * IBC Height representation
 */
@Serializable
data class Height(
    @SerialName("revision_number") val revisionNumber: Long = 0,
    @SerialName("revision_height") val revisionHeight: Long = 1
) {
    val isZero: Boolean get() = revisionNumber == 0L && revisionHeight == 0L

    fun increment(): Height = copy(revisionHeight = revisionHeight + 1)
}

/**
 * Chain identifier
 */
@JvmInline
value class ChainId(val id: String)

/**
 * IBC Version with features
 */
@Serializable
data class Version(
    val identifier: String,
    val features: List<String>
) {
    companion object {
        fun defaultConnection() = Version("1", listOf("ORDER_ORDERED", "ORDER_UNORDERED"))
    }
}

/**
 * Light client types
 */
enum class ClientType(val value: String) {
    TENDERMINT("tendermint"),
    SOLO_MACHINE("solo_machine"),
    LOCALHOST("localhost"),
    WASM("wasm")
}

/**
 * Light client identifier
 */
@JvmInline
value class ClientId(val id: String)

/**
 * Trust level configuration
 */
@Serializable
data class TrustLevel(
    val numerator: Int = 1,
    val denominator: Int = 3
)

/**
 * Light client state
 */
@Serializable
data class ClientState(
    @SerialName("chain_id") val chainId: String,
    @SerialName("trust_level") val trustLevel: TrustLevel,
    @SerialName("trusting_period") val trustingPeriod: Long,
    @SerialName("unbonding_period") val unbondingPeriod: Long,
    @SerialName("max_clock_drift") val maxClockDrift: Long,
    @SerialName("latest_height") val latestHeight: Height,
    @SerialName("frozen_height") val frozenHeight: Height? = null
)

/**
 * Connection state
 */
enum class ConnectionState {
    UNINITIALIZED, INIT, TRYOPEN, OPEN
}

/**
 * Connection identifier
 */
@JvmInline
value class ConnectionId(val id: String) {
    companion object {
        fun newId(sequence: Int) = ConnectionId("connection-$sequence")
    }
}

/**
 * Port identifier
 */
@JvmInline
value class PortId(val id: String) {
    companion object {
        fun transfer() = PortId("transfer")
    }
}

/**
 * Channel identifier
 */
@JvmInline
value class ChannelId(val id: String) {
    companion object {
        fun newId(sequence: Int) = ChannelId("channel-$sequence")
    }
}

/**
 * Channel ordering
 */
enum class ChannelOrder {
    UNORDERED, ORDERED
}

/**
 * Channel state
 */
enum class ChannelState {
    UNINITIALIZED, INIT, TRYOPEN, OPEN, CLOSED
}

/**
 * Timeout information
 */
data class Timeout(
    val height: Height,
    val timestamp: BigInteger = BigInteger.ZERO
) {
    companion object {
        fun fromHeight(height: Long) = Timeout(Height(revisionHeight = height))
        fun fromTimestamp(timestamp: BigInteger) = Timeout(Height(), timestamp)
    }

    fun toJson(): Map<String, Any> = mapOf(
        "height" to mapOf(
            "revision_number" to height.revisionNumber,
            "revision_height" to height.revisionHeight
        ),
        "timestamp" to timestamp.toString()
    )
}

/**
 * Transfer packet data (ICS-20)
 */
@Serializable
data class FungibleTokenPacketData(
    val denom: String,
    val amount: String,
    val sender: String,
    val receiver: String,
    val memo: String = ""
) {
    val isNative: Boolean get() = !denom.contains("/")
}

/**
 * Swap state
 */
enum class SwapState {
    PENDING, LOCKED, COMPLETED, REFUNDED, EXPIRED, CANCELLED
}

/**
 * Swap identifier
 */
@JvmInline
value class SwapId(val id: String)

/**
 * Native asset for swaps
 */
data class NativeAsset(val amount: BigInteger) {
    fun toJson(): Map<String, Any> = mapOf("native" to mapOf("amount" to amount.toString()))
}

/**
 * ICS-20 asset for swaps
 */
data class Ics20Asset(val denom: String, val amount: BigInteger) {
    fun toJson(): Map<String, Any> = mapOf(
        "ics20" to mapOf("denom" to denom, "amount" to amount.toString())
    )
}

/**
 * Hashlock - hash of the secret
 */
data class Hashlock(val hash: ByteArray) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Hashlock) return false
        return hash.contentEquals(other.hash)
    }

    override fun hashCode(): Int = hash.contentHashCode()
}

/**
 * Timelock - expiration time
 */
data class Timelock(val expiry: BigInteger) {
    fun isExpired(current: BigInteger): Boolean = current >= expiry
}

/**
 * Atomic swap
 */
data class AtomicSwap(
    val swapId: SwapId,
    val state: SwapState,
    val initiatorHtlc: Map<String, Any>,
    val responderHtlc: Map<String, Any>?
) {
    companion object {
        @Suppress("UNCHECKED_CAST")
        fun fromJson(json: Map<String, Any>): AtomicSwap {
            val swapIdMap = json["swap_id"] as Map<String, Any>
            return AtomicSwap(
                swapId = SwapId(swapIdMap["id"] as String),
                state = SwapState.valueOf((json["state"] as String).uppercase()),
                initiatorHtlc = json["initiator_htlc"] as Map<String, Any>,
                responderHtlc = json["responder_htlc"] as? Map<String, Any>
            )
        }
    }
}

/**
 * IBC SDK configuration
 */
data class IbcConfig(
    val apiKey: String,
    val endpoint: String = "https://ibc.synor.io/v1",
    val wsEndpoint: String = "wss://ibc.synor.io/v1/ws",
    val timeout: Int = 30,
    val retries: Int = 3,
    val chainId: String = "synor-1",
    val debug: Boolean = false
)

/**
 * IBC exception
 */
class IbcException(
    message: String,
    val code: String? = null,
    val status: Int? = null
) : RuntimeException(message) {
    override fun toString(): String =
        "IbcException: $message${code?.let { " ($it)" } ?: ""}"
}
