package dev.quicproquo

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.Closeable

/**
 * High-level quicproquo client for Android/JVM.
 *
 * Wraps `libquicproquo_ffi` via JNI to provide a Kotlin-native API.
 *
 * ```kotlin
 * val client = QpqClient("127.0.0.1:5001", caCertPath = "ca.pem")
 * client.login("alice", "secret")
 * client.send("bob", "hello".toByteArray())
 * val messages = client.receive(timeoutMs = 5000)
 * client.close()
 * ```
 */
class QpqClient(
    server: String,
    caCertPath: String,
    serverName: String = server.substringBefore(":")
) : Closeable {

    @Volatile
    private var handle: Long

    private val gson = Gson()

    init {
        val h = NativeBridge.nativeConnect(server, caCertPath, serverName)
        if (h == 0L) {
            throw QpqException("qpq_connect failed for $server")
        }
        handle = h
    }

    /** Whether the client is connected. */
    val isConnected: Boolean
        get() = handle != 0L

    /**
     * Authenticate with OPAQUE credentials.
     *
     * @throws QpqAuthException on bad credentials.
     */
    fun login(username: String, password: String) {
        checkConnected()
        val code = NativeBridge.nativeLogin(handle, username, password)
        checkStatus(code)
    }

    /**
     * Send a message to a recipient by username.
     *
     * @param recipient Recipient username.
     * @param message Message payload (arbitrary bytes).
     */
    fun send(recipient: String, message: ByteArray) {
        checkConnected()
        val code = NativeBridge.nativeSend(handle, recipient, message)
        checkStatus(code)
    }

    /**
     * Receive pending messages, blocking up to [timeoutMs] milliseconds.
     *
     * @return List of message strings (UTF-8).
     */
    fun receive(timeoutMs: Int = 5000): List<String> {
        checkConnected()
        val json = NativeBridge.nativeReceive(handle, timeoutMs) ?: return emptyList()
        val listType = object : TypeToken<List<String>>() {}.type
        return gson.fromJson(json, listType)
    }

    /** Disconnect from the server and release resources. */
    override fun close() {
        val h = handle
        if (h != 0L) {
            handle = 0L
            NativeBridge.nativeDisconnect(h)
        }
    }

    /** Alias for [close]. */
    fun disconnect() = close()

    private fun checkConnected() {
        if (handle == 0L) throw QpqNotConnectedException()
    }

    private fun checkStatus(code: Int) {
        if (code == NativeBridge.QPQ_OK) return

        val msg = NativeBridge.nativeLastError(handle) ?: "unknown error"
        throw when (code) {
            NativeBridge.QPQ_AUTH_FAILED -> QpqAuthException(msg)
            NativeBridge.QPQ_TIMEOUT -> QpqTimeoutException(msg)
            NativeBridge.QPQ_NOT_CONNECTED -> QpqNotConnectedException()
            else -> QpqException(msg)
        }
    }
}
