package net.hivetechnology.o20010_06.model

import org.json.JSONArray
import org.json.JSONObject

enum class SessionStatus(val wireValue: String) {
    CLAIMED("claimed"),
    AWAITING_PERMISSION("awaiting-permission"),
    WAITING_RUNTIME("waiting-runtime"),
    CONNECTING("connecting"),
    CONNECTED("connected"),
    RECONNECTING("reconnecting"),
    STOPPED("stopped"),
    FAILED("failed"),
    EXPIRED("expired");

    companion object {
        fun from(value: String?): SessionStatus = entries.firstOrNull { it.wireValue == value } ?: FAILED
    }
}

data class DeviceSummary(val id: Long, val name: String, val version: String)

data class MobileSession(
    val id: String,
    val status: SessionStatus,
    val device: DeviceSummary? = null,
    val connectionId: Long? = null,
    val transport: String? = null,
    val quality: String? = null,
    val latencyMs: Long? = null,
    val errorCode: String? = null,
    val errorMessage: String? = null,
) {
    companion object {
        fun fromJson(json: JSONObject): MobileSession {
            val deviceJson = json.optJSONObject("device")
            return MobileSession(
                id = json.getString("id"),
                status = SessionStatus.from(json.optString("status")),
                device = deviceJson?.let {
                    DeviceSummary(it.optLong("id"), it.optString("name"), it.optString("version"))
                },
                connectionId = json.optLongOrNull("connectionId"),
                transport = json.optNullableString("transport"),
                quality = json.optNullableString("quality"),
                latencyMs = json.optLongOrNull("latencyMs"),
                errorCode = json.optNullableString("errorCode"),
                errorMessage = json.optNullableString("errorMessage"),
            )
        }
    }
}

data class SessionCredentials(
    val sessionId: String,
    val token: String,
    val websocketUrl: String,
    val deviceName: String,
    val portalReturnUrl: String = "https://portal.hivetechnology.net/",
) {
    fun toJson() = JSONObject()
        .put("sessionId", sessionId)
        .put("token", token)
        .put("websocketUrl", websocketUrl)
        .put("deviceName", deviceName)
        .put("portalReturnUrl", portalReturnUrl)

    companion object {
        fun fromJson(json: JSONObject) = SessionCredentials(
            sessionId = json.getString("sessionId"),
            token = json.getString("token"),
            websocketUrl = json.getString("websocketUrl"),
            deviceName = json.optString("deviceName", "Device"),
            portalReturnUrl = json.optString("portalReturnUrl", "https://portal.hivetechnology.net/"),
        )
    }
}

data class RedeemResult(val session: MobileSession, val credentials: SessionCredentials)

data class VpnRoute(val address: String, val mask: String, val source: String, val interfaceName: String?)
data class RuntimeTls(val caCertificatePem: String?, val certificateSha256: String?)
data class RuntimeTarget(val host: String, val port: Int, val secure: Boolean, val path: String, val tls: RuntimeTls)
data class N3nSettings(
    val supernodes: List<String>,
    val community: String,
    val key: String,
    val preferredTransport: String,
    val fallbackTransport: String,
    val transportOrder: List<String>,
)
data class OverlaySettings(val address: String, val serverAddress: String, val prefixLength: Int, val mtu: Int)
data class VpnConfiguration(
    val configVersion: Int,
    val sessionId: String,
    val connectionId: Long,
    val edgeSessionId: String?,
    val edgeGeneration: Long,
    val n3n: N3nSettings,
    val overlay: OverlaySettings,
    val routes: List<VpnRoute>,
    val runtime: RuntimeTarget,
) {
    companion object {
        fun fromJson(json: JSONObject): VpnConfiguration {
            val configVersion = json.getInt("configVersion")
            require(configVersion == 1 || configVersion == 2) { "Unsupported mobile VPN configuration" }
            val n3n = json.getJSONObject("n3n")
            require(n3n.getString("version") == "n3n-v3") { "Unsupported mobile VPN transport" }
            val overlay = json.getJSONObject("overlay")
            val runtime = json.getJSONObject("runtime")
            val tls = runtime.optJSONObject("tls") ?: JSONObject()
            return VpnConfiguration(
                configVersion = configVersion,
                sessionId = json.getString("sessionId"),
                connectionId = json.getLong("connectionId"),
                edgeSessionId = json.optNullableString("edgeSessionId"),
                edgeGeneration = json.optLong("edgeGeneration", 1L),
                n3n = N3nSettings(
                    supernodes = n3n.getJSONArray("supernodeUrls").strings(),
                    community = n3n.getString("community"),
                    key = n3n.getString("key"),
                    preferredTransport = n3n.optString("preferredTransport", "udp-p2p"),
                    fallbackTransport = n3n.optString("fallbackTransport", "tcp-relay"),
                    transportOrder = normalizedTransportOrder(
                        n3n.optJSONArray("transportOrder")?.strings().orEmpty(),
                    ),
                ),
                overlay = OverlaySettings(
                    address = overlay.getString("address"),
                    serverAddress = overlay.getString("serverAddress"),
                    prefixLength = overlay.getInt("prefixLength"),
                    mtu = overlay.optInt("mtu", 1290),
                ),
                routes = json.optJSONArray("routes")?.objects()?.map {
                    VpnRoute(it.getString("ip"), it.getString("mask"), it.optString("source"), it.optNullableString("interfaceName"))
                } ?: emptyList(),
                runtime = RuntimeTarget(
                    host = runtime.getString("host"),
                    port = runtime.getInt("port"),
                    secure = runtime.optBoolean("secure", true),
                    path = runtime.getString("path"),
                    tls = RuntimeTls(tls.optNullableString("caCertificatePem"), tls.optNullableString("certificateSha256")),
                ),
            )
        }
    }
}

private fun normalizedTransportOrder(configured: List<String>): List<String> {
    val supported = listOf("udp-adaptive", "udp-p2p", "udp-relay", "tcp-relay")
    val selected = configured.filter { it in supported }.distinct()
    return if (selected.isEmpty()) listOf("udp-adaptive") else selected
}

fun JSONObject.optNullableString(name: String): String? =
    if (!has(name) || isNull(name)) null else optString(name).takeIf { it.isNotBlank() }

fun JSONObject.optLongOrNull(name: String): Long? =
    if (!has(name) || isNull(name)) null else optLong(name)

fun JSONArray.strings(): List<String> = (0 until length()).map { getString(it) }
fun JSONArray.objects(): List<JSONObject> = (0 until length()).map { getJSONObject(it) }
