package com.example.vastlib.utils

import com.example.vastlib.entity.web_socket.WsRequest
import com.example.vastlib.entity.web_socket.WsResponse
import com.example.vastlib.service.BaseActionExecService
import kotlinx.coroutines.delay
import org.java_websocket.client.WebSocketClient
import org.java_websocket.drafts.Draft_6455
import org.java_websocket.extensions.IExtension
import org.java_websocket.handshake.ServerHandshake
import org.java_websocket.protocols.IProtocol
import org.java_websocket.protocols.Protocol
import org.json.JSONArray
import java.lang.Exception
import java.net.URI

class WebSocketClientManager {
    private var currentClient: WebSocketClient? = null

    companion object {
        private var interrupt = ""
        const val WEB_SOCKET_REQUEST_METHOD = "WS"
        const val WEB_SOCKET_REQUEST_HEADER_PROTOCOL = "Sec-WebSocket-Protocol"


        const val ERROR_CODE_WS_BUILD_CONNECTION_FAILED = 701
        const val ERROR_CODE_WS_ESTABLISH_CONNECTION_EXCEPTION = 702
        const val ERROR_CODE_WS_ESTABLISH_CONNECTION_FAILED = 703
        const val ERROR_CODE_WS_SEND_MESSAGE_FAILED = 704
        const val ERROR_CODE_CALL_WEB_SOCKET_FAILED = 705

        private const val WS_EVENT_BUILD_CONNECTION_SUCCESS = 110
        private const val WS_EVENT_CONNECT_SUCCESS = 111
        private const val WS_EVENT_ALL_MSG_SENT = 112

        private var webSocketClient: WebSocketClientManager? = null
        private val lock: Any = Any()

        fun getInstance(): WebSocketClientManager = webSocketClient ?: synchronized(lock) {
            webSocketClient ?: WebSocketClientManager().apply {
                webSocketClient = this
            }
            webSocketClient!!
        }
    }

    suspend fun callWebSocketRequest(request: WsRequest): WsResponse {
        val startTime = System.currentTimeMillis()
        val result = WsResponse(
            startTime = startTime
        )
        val responseHeaders: MutableMap<String, String> = mutableMapOf()
        val responseData = JSONArray()
        var respCode = BaseActionExecService.WEB_SOCKET_CODE
        var errorMessage = ""

        kotlin.runCatching {
            if (currentClient == null || currentClient?.isOpen == true ||
                currentClient?.uri.toString() == request.url
            ) {

                currentClient?.let {
                    currentClient?.runCatching {
                        close()
                    }
                    currentClient = null
                }

                var subProtocol = ""
                val headers = request.headers
                headers.filter {
                    it.key.equals(
                        WEB_SOCKET_REQUEST_HEADER_PROTOCOL,
                        ignoreCase = true
                    )
                }.map {
                    subProtocol = headers.getOrElse(it.key) { "" }
                    it.key
                }.forEach {
                    headers.remove(it)
                }
                val knownExtensions = mutableListOf<IExtension>()
                val knownProtocols = mutableListOf<IProtocol>()
                knownProtocols += Protocol(subProtocol)
                val draft = Draft_6455(knownExtensions, knownProtocols)
                val webSocketClient = kotlin.runCatching {
                    val wc = object : WebSocketClient(URI.create(request.url), draft, headers) {
                        override fun onOpen(p0: ServerHandshake?) {
                            LogUtils.info("${System.currentTimeMillis()} connection opened")
                        }

                        override fun onMessage(p0: String?) {
                            LogUtils.info("${System.currentTimeMillis()} receive message $p0")
                            if (interrupt.isNotBlank() &&
                                "*" == interrupt &&
                                interrupt == p0
                            ) {
                                this.close()
                            }
                            responseData.put(p0)
                        }

                        override fun onClose(p0: Int, p1: String?, p2: Boolean) {
                            LogUtils.info("${System.currentTimeMillis()} current web socket connection disconnected")
                        }

                        override fun onError(p0: Exception?) {
                            LogUtils.info("${System.currentTimeMillis()} current web socket connection error")
                        }

                    }
                    responseHeaders["${System.currentTimeMillis()}"] =
                        "$WS_EVENT_BUILD_CONNECTION_SUCCESS"

                    wc
                }.onFailure {
                    respCode = ERROR_CODE_WS_BUILD_CONNECTION_FAILED
                    LogUtils.error(throwable = it)
                    errorMessage = "${it.message}"
                    return@callWebSocketRequest result
                }.getOrNull()
                LogUtils.info("${System.currentTimeMillis()} start connect web socket ${webSocketClient?.uri.toString()}")

                kotlin.runCatching {
                    webSocketClient?.connect()
                    var waitCount = 0
                    while (webSocketClient?.isOpen == false && waitCount < 10) {
                        LogUtils.info("Connecting...")
                        delay(1000)
                        waitCount++
                    }
                }.onFailure {
                    respCode = ERROR_CODE_WS_BUILD_CONNECTION_FAILED
                    LogUtils.error(throwable = it)
                    errorMessage = "${it.message}"
                    return@callWebSocketRequest result
                }

                if (webSocketClient?.isOpen == true) {
                    responseHeaders["${System.currentTimeMillis()}"] = "$WS_EVENT_CONNECT_SUCCESS"
                    currentClient = webSocketClient
                }
            } else {
                LogUtils.info("use last time web socket connection")
            }

            if(!sendMessage(request, responseHeaders, respCode, errorMessage)){
                return@callWebSocketRequest result
            }
        }.onFailure {
            respCode = ERROR_CODE_CALL_WEB_SOCKET_FAILED
            LogUtils.error(throwable = it)
            errorMessage = "${it.message}"
        }
        if (responseData.length() < 1 && errorMessage.isNotBlank()) {
            responseData.put(errorMessage)
        }

        result.data = responseData
        result.headers = responseHeaders
        result.endTime = System.currentTimeMillis()
        result.code = respCode

        return result
    }

    private suspend fun sendMessage(
        request: WsRequest,
        responseHeaders: MutableMap<String, String>,
        respCode: Int,
        errorMessage: String,
    ):Boolean {
        var respCode1 = respCode
        var errorMessage1 = errorMessage
        val messages = request.messages
        if (messages.isNotEmpty() && currentClient?.isOpen == true) {
            kotlin.runCatching {
                messages.forEach {
                    interrupt = if (interrupt.isBlank()) {
                        ""
                    } else {
                        it.interrupt
                    }
                    if (currentClient?.isOpen == true) {
                        currentClient?.send(it.value)
                        responseHeaders["${System.currentTimeMillis()}"] = it.value
                        LogUtils.info("${System.currentTimeMillis()} send message ${it.value}")
                        if (it.wait_time > 0) {
                            delay(it.wait_time)
                        }
                    }
                }
            }.onFailure {
                respCode1 = ERROR_CODE_WS_SEND_MESSAGE_FAILED
                LogUtils.error(throwable = it)
                errorMessage1 = "${it.message}"
                return@sendMessage false
            }

            responseHeaders["${System.currentTimeMillis()}"] = "$WS_EVENT_ALL_MSG_SENT"
            if (request.delay > 0) {
                delay(request.delay * 1000L)
            }
        } else {
            respCode1 = ERROR_CODE_WS_ESTABLISH_CONNECTION_FAILED
        }
        return true
    }

    fun closeClient() {
        LogUtils.info("${System.currentTimeMillis()} receive msg to close client")
        currentClient?.let {
            kotlin.runCatching {
                it.close()
            }
            currentClient = null
        }
    }
}