package net.hivetechnology.o20010_06.vpn

import net.hivetechnology.o20010_06.model.MobileSession
import net.hivetechnology.o20010_06.model.SessionCredentials
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import org.json.JSONObject
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean

class SessionEventSocket(
    private val credentials: SessionCredentials,
    private val onSession: (MobileSession) -> Unit,
) : AutoCloseable {
    private val client = OkHttpClient.Builder().pingInterval(20, TimeUnit.SECONDS).build()
    private val heartbeatExecutor = Executors.newSingleThreadScheduledExecutor()
    private val closed = AtomicBoolean(false)
    private val connecting = AtomicBoolean(false)
    @Volatile private var socket: WebSocket? = null
    @Volatile private var heartbeat: ScheduledFuture<*>? = null
    @Volatile private var reconnect: ScheduledFuture<*>? = null

    @Synchronized
    fun connect() {
        if (closed.get() || !connecting.compareAndSet(false, true)) return
        val request = Request.Builder()
            .url(credentials.websocketUrl)
            .header("Authorization", "Bearer ${credentials.token}")
            .header("X-HT-Mobile-VPN-Session", credentials.sessionId)
            .build()
        socket = client.newWebSocket(request, object : WebSocketListener() {
            override fun onOpen(webSocket: WebSocket, response: Response) {
                connectionOpened(webSocket)
            }

            override fun onMessage(webSocket: WebSocket, text: String) {
                runCatching {
                    val message = JSONObject(text)
                    if (message.optString("command") == "MOBILE_VPN_SESSION_EVENT") {
                        onSession(MobileSession.fromJson(message.getJSONObject("session")))
                    }
                }
            }

            override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
                connectionEnded(webSocket)
            }

            override fun onFailure(webSocket: WebSocket, throwable: Throwable, response: Response?) {
                connectionEnded(webSocket)
            }
        })
    }

    @Synchronized
    private fun connectionOpened(webSocket: WebSocket) {
        if (socket !== webSocket) {
            webSocket.close(1000, "superseded connection")
            return
        }
        connecting.set(false)
        reconnect?.cancel(false)
        reconnect = null
        if (closed.get()) {
            webSocket.close(1000, "client stopped")
            return
        }
        webSocket.send("{\"type\":\"heartbeat\"}")
        heartbeat?.cancel(false)
        heartbeat = heartbeatExecutor.scheduleWithFixedDelay(
            {
                if (socket === webSocket && !webSocket.send("{\"type\":\"heartbeat\"}")) {
                    connectionEnded(webSocket)
                }
            },
            20,
            20,
            TimeUnit.SECONDS,
        )
    }

    @Synchronized
    private fun connectionEnded(webSocket: WebSocket) {
        if (socket !== webSocket) return
        socket = null
        connecting.set(false)
        heartbeat?.cancel(false)
        heartbeat = null
        if (closed.get() || reconnect != null) return
        reconnect = heartbeatExecutor.schedule({
            synchronized(this) { reconnect = null }
            connect()
        }, 2, TimeUnit.SECONDS)
    }

    @Synchronized
    override fun close() {
        if (!closed.compareAndSet(false, true)) return
        reconnect?.cancel(false)
        reconnect = null
        heartbeat?.cancel(false)
        heartbeat = null
        val current = socket
        socket = null
        current?.close(1000, "client stopped")
        heartbeatExecutor.shutdownNow()
        client.dispatcher.executorService.shutdown()
    }
}
