package com.kaixed.kchat.service

import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson
import com.kaixed.kchat.data.objectbox.ObjectBox
import com.kaixed.kchat.data.objectbox.entity.Messages
import com.kaixed.kchat.network.NetworkInterface.WEBSOCKET
import com.kaixed.kchat.network.NetworkInterface.WEBSOCKET_SERVER_URL
import com.kaixed.kchat.network.OkhttpHelper
import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.tencent.mmkv.MMKV
import io.objectbox.Box
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener

class WebSocketService : Service() {

    companion object {
        private const val TAG = "WebSocketService"
        private const val WEBSOCKET_CLOSE_CODE = 1000
    }

    private lateinit var messagesBox: Box<Messages>
    private lateinit var username: String
    private val binder = LocalBinder()
    private val messagesMutableLiveData = MutableLiveData<Messages>()
    private var webSocket: WebSocket? = null
    private var heartbeatJob: Job? = null

    private val serviceJob = Job()
    private val serviceScope = CoroutineScope(IO + serviceJob)

    val liveData: LiveData<Messages> get() = messagesMutableLiveData

    inner class LocalBinder : Binder() {
        fun getService(): WebSocketService {
            return this@WebSocketService
        }
    }

    override fun onBind(intent: Intent?): IBinder {
        return binder
    }

    override fun onCreate() {
        super.onCreate()
        messagesBox = ObjectBox.get().boxFor(Messages::class.java)
        username = getUsername()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        establishConnection()
        return START_STICKY
    }

    fun sendMessage(jsonObject: String, msgLocalId: Long) {
        webSocket?.let {
            it.send(jsonObject)
            val kv = MMKV.mmkvWithID(MMKV_COMMON_DATA)
            kv.putLong("msgLocalId", msgLocalId)
        }
    }

    private fun establishConnection() {
        if (webSocket == null) {
            val request = Request.Builder()
                .url("$WEBSOCKET_SERVER_URL$WEBSOCKET$username").build()
            val listener = EchoWebSocketListener()
            val client = OkhttpHelper.getInstance()
            webSocket = client.newWebSocket(request, listener)
        }
    }

    private inner class EchoWebSocketListener : WebSocketListener() {
        override fun onOpen(webSocket: WebSocket, response: Response) {
            Log.d(TAG, "WebSocket Opened")
        }

        override fun onMessage(webSocket: WebSocket, text: String) {
            val messages = Gson().fromJson(text, Messages::class.java)
            messages.takerId = messages.senderId
            messagesMutableLiveData.postValue(messages)

            serviceScope.launch {
                if ("ack" == messages.type) {
                    val existingMessage = messagesBox.get(messages.msgLocalId)
                    existingMessage?.let {
                        it.timestamp = messages.timestamp
                        it.msgSvrId = messages.msgSvrId
                        messagesBox.put(it)
                    } ?: run {
                        messagesBox.put(messages)
                    }
                } else {
                    messagesBox.put(messages)
                }
                Log.d(TAG, "Message stored: $messages")
            }
        }

        override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
            webSocket.close(WEBSOCKET_CLOSE_CODE, null)
            Log.d(TAG, "WebSocket closing: $reason")
            establishConnection()
        }

        override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
            establishConnection()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        serviceJob.cancel()
        heartbeatJob?.cancel()
        webSocket?.close(WEBSOCKET_CLOSE_CODE, "App exited")
    }
}
