package io.synor.crypto

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.*
import java.util.Base64
import java.util.concurrent.atomic.AtomicBoolean

/**
 * Synor Crypto SDK for Kotlin
 *
 * Quantum-resistant cryptographic primitives for the Synor blockchain.
 *
 * Example:
 * ```kotlin
 * val config = CryptoConfig("your-api-key")
 * val crypto = SynorCrypto(config)
 *
 * // Generate a mnemonic
 * val mnemonic = crypto.mnemonic().generate(24)
 * println("Backup words: ${mnemonic.phrase}")
 *
 * // Create keypair from mnemonic
 * val keypair = crypto.keypairs().fromMnemonic(mnemonic.phrase, "")
 * val address = keypair.getAddress(Network.MAINNET)
 *
 * // Sign a message
 * val signature = crypto.signing().sign(keypair, "Hello!".toByteArray())
 * ```
 */
class SynorCrypto(private val config: CryptoConfig) {
    private val httpClient: HttpClient
    private val json = Json {
        ignoreUnknownKeys = true
        namingStrategy = JsonNamingStrategy.SnakeCase
    }
    private val closed = AtomicBoolean(false)

    private val mnemonicClient: MnemonicClient
    private val keypairClient: KeypairClient
    private val signingClient: SigningClient
    private val falconClient: FalconClient
    private val sphincsClient: SphincsClient
    private val kdfClient: KdfClient
    private val hashClient: HashClient

    init {
        httpClient = HttpClient(CIO) {
            install(ContentNegotiation) {
                json(json)
            }
            install(HttpTimeout) {
                requestTimeoutMillis = config.timeout
                connectTimeoutMillis = config.timeout
            }
            defaultRequest {
                header("Authorization", "Bearer ${config.apiKey}")
                header("X-SDK-Version", "kotlin/0.1.0")
            }
        }

        mnemonicClient = MnemonicClient(this)
        keypairClient = KeypairClient(this)
        signingClient = SigningClient(this)
        falconClient = FalconClient(this)
        sphincsClient = SphincsClient(this)
        kdfClient = KdfClient(this)
        hashClient = HashClient(this)
    }

    fun getDefaultNetwork(): Network = config.defaultNetwork

    fun mnemonic(): MnemonicClient = mnemonicClient
    fun keypairs(): KeypairClient = keypairClient
    fun signing(): SigningClient = signingClient
    fun falcon(): FalconClient = falconClient
    fun sphincs(): SphincsClient = sphincsClient
    fun kdf(): KdfClient = kdfClient
    fun hash(): HashClient = hashClient

    suspend fun healthCheck(): Boolean = try {
        val result = get<Map<String, Any>>("/health")
        result["status"] == "healthy"
    } catch (e: Exception) {
        false
    }

    suspend fun getInfo(): Map<String, Any> = get("/info")

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

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

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

    internal suspend inline fun <reified T> post(path: String, body: Any? = null): T {
        checkClosed()
        val response = httpClient.post("${config.endpoint}$path") {
            contentType(ContentType.Application.Json)
            setBody(body ?: emptyMap<String, Any>())
        }
        return handleResponse(response)
    }

    @PublishedApi
    internal suspend inline fun <reified T> handleResponse(response: HttpResponse): T {
        if (response.status.value >= 400) {
            val errorBody = response.bodyAsText()
            val error = try {
                json.decodeFromString<Map<String, String>>(errorBody)
            } catch (e: Exception) {
                emptyMap()
            }
            throw CryptoException(
                error["message"] ?: "HTTP ${response.status.value}",
                error["code"]
            )
        }
        return response.body()
    }

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

    // ========================================================================
    // Sub-Clients
    // ========================================================================

    class MnemonicClient(private val crypto: SynorCrypto) {
        suspend fun generate(wordCount: Int = 24): Mnemonic =
            crypto.post("/mnemonic/generate", mapOf("word_count" to wordCount))

        suspend fun fromPhrase(phrase: String): Mnemonic =
            crypto.post("/mnemonic/from-phrase", mapOf("phrase" to phrase))

        suspend fun validate(phrase: String): MnemonicValidation =
            crypto.post("/mnemonic/validate", mapOf("phrase" to phrase))

        suspend fun toSeed(phrase: String, passphrase: String = ""): ByteArray {
            val result: Map<String, String> = crypto.post(
                "/mnemonic/to-seed",
                mapOf("phrase" to phrase, "passphrase" to passphrase)
            )
            return Base64.getDecoder().decode(result["seed"])
        }

        suspend fun suggestWords(partial: String, limit: Int = 5): List<String> {
            val result: Map<String, List<String>> = crypto.post(
                "/mnemonic/suggest",
                mapOf("partial" to partial, "limit" to limit)
            )
            return result["suggestions"] ?: emptyList()
        }
    }

    class KeypairClient(private val crypto: SynorCrypto) {
        suspend fun generate(): HybridKeypair =
            crypto.post("/keypair/generate")

        suspend fun fromMnemonic(phrase: String, passphrase: String = ""): HybridKeypair =
            crypto.post("/keypair/from-mnemonic", mapOf("phrase" to phrase, "passphrase" to passphrase))

        suspend fun fromSeed(seed: ByteArray): HybridKeypair =
            crypto.post("/keypair/from-seed", mapOf("seed" to Base64.getEncoder().encodeToString(seed)))

        suspend fun getAddress(publicKey: HybridPublicKey, network: Network): Address =
            crypto.post("/keypair/address", mapOf("public_key" to publicKey.toMap(), "network" to network.value))
    }

    class SigningClient(private val crypto: SynorCrypto) {
        suspend fun sign(keypair: HybridKeypair, message: ByteArray): HybridSignature =
            crypto.post("/sign/hybrid", mapOf(
                "secret_key" to keypair.secretKey.toMap(),
                "message" to Base64.getEncoder().encodeToString(message)
            ))

        suspend fun verify(publicKey: HybridPublicKey, message: ByteArray, signature: HybridSignature): Boolean {
            val result: Map<String, Boolean> = crypto.post("/sign/verify", mapOf(
                "public_key" to publicKey.toMap(),
                "message" to Base64.getEncoder().encodeToString(message),
                "signature" to signature.toMap()
            ))
            return result["valid"] ?: false
        }

        suspend fun signEd25519(secretKey: ByteArray, message: ByteArray): ByteArray {
            val result: Map<String, String> = crypto.post("/sign/ed25519", mapOf(
                "secret_key" to Base64.getEncoder().encodeToString(secretKey),
                "message" to Base64.getEncoder().encodeToString(message)
            ))
            return Base64.getDecoder().decode(result["signature"])
        }
    }

    class FalconClient(private val crypto: SynorCrypto) {
        suspend fun generate(variant: FalconVariant = FalconVariant.FALCON512): FalconKeypair =
            crypto.post("/falcon/generate", mapOf("variant" to variant.value))

        suspend fun sign(keypair: FalconKeypair, message: ByteArray): FalconSignature =
            crypto.post("/falcon/sign", mapOf(
                "variant" to keypair.getVariantEnum().value,
                "secret_key" to Base64.getEncoder().encodeToString(keypair.secretKey.getKeyBytes()),
                "message" to Base64.getEncoder().encodeToString(message)
            ))

        suspend fun verify(publicKey: ByteArray, message: ByteArray, signature: FalconSignature): Boolean {
            val result: Map<String, Boolean> = crypto.post("/falcon/verify", mapOf(
                "variant" to signature.getVariantEnum().value,
                "public_key" to Base64.getEncoder().encodeToString(publicKey),
                "message" to Base64.getEncoder().encodeToString(message),
                "signature" to Base64.getEncoder().encodeToString(signature.getBytes())
            ))
            return result["valid"] ?: false
        }
    }

    class SphincsClient(private val crypto: SynorCrypto) {
        suspend fun generate(variant: SphincsVariant = SphincsVariant.SHAKE128S): SphincsKeypair =
            crypto.post("/sphincs/generate", mapOf("variant" to variant.value))

        suspend fun sign(keypair: SphincsKeypair, message: ByteArray): SphincsSignature =
            crypto.post("/sphincs/sign", mapOf(
                "variant" to keypair.getVariantEnum().value,
                "secret_key" to Base64.getEncoder().encodeToString(keypair.secretKey.getKeyBytes()),
                "message" to Base64.getEncoder().encodeToString(message)
            ))

        suspend fun verify(publicKey: ByteArray, message: ByteArray, signature: SphincsSignature): Boolean {
            val result: Map<String, Boolean> = crypto.post("/sphincs/verify", mapOf(
                "variant" to signature.getVariantEnum().value,
                "public_key" to Base64.getEncoder().encodeToString(publicKey),
                "message" to Base64.getEncoder().encodeToString(message),
                "signature" to Base64.getEncoder().encodeToString(signature.getBytes())
            ))
            return result["valid"] ?: false
        }
    }

    class KdfClient(private val crypto: SynorCrypto) {
        suspend fun deriveKey(seed: ByteArray, config: DerivationConfig = DerivationConfig()): ByteArray {
            val body = mutableMapOf<String, Any>(
                "seed" to Base64.getEncoder().encodeToString(seed),
                "output_length" to config.outputLength
            )
            config.salt?.let { body["salt"] = Base64.getEncoder().encodeToString(it) }
            config.info?.let { body["info"] = Base64.getEncoder().encodeToString(it) }

            val result: Map<String, String> = crypto.post("/kdf/hkdf", body)
            return Base64.getDecoder().decode(result["key"])
        }

        suspend fun deriveFromPassword(password: ByteArray, config: PasswordDerivationConfig): ByteArray {
            val result: Map<String, String> = crypto.post("/kdf/pbkdf2", mapOf(
                "password" to Base64.getEncoder().encodeToString(password),
                "salt" to Base64.getEncoder().encodeToString(config.salt),
                "iterations" to config.iterations,
                "output_length" to config.outputLength
            ))
            return Base64.getDecoder().decode(result["key"])
        }
    }

    class HashClient(private val crypto: SynorCrypto) {
        suspend fun sha3_256(data: ByteArray): Hash256 =
            crypto.post("/hash/sha3-256", mapOf("data" to Base64.getEncoder().encodeToString(data)))

        suspend fun blake3(data: ByteArray): Hash256 =
            crypto.post("/hash/blake3", mapOf("data" to Base64.getEncoder().encodeToString(data)))

        suspend fun keccak256(data: ByteArray): Hash256 =
            crypto.post("/hash/keccak256", mapOf("data" to Base64.getEncoder().encodeToString(data)))
    }
}
