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.get
import com.kaixed.kchat.data.objectbox.entity.Conversation
import com.kaixed.kchat.data.objectbox.entity.Conversation_
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.getCurrentContactId
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 val conversationBox: Box<Conversation> by lazy { get().boxFor(Conversation::class.java) }

    private lateinit var username: String

    private val binder = LocalBinder()

    private var webSocket: WebSocket? = null

    private var heartbeatJob: Job? = null

    private val serviceJob = Job()

    private val serviceScope = CoroutineScope(IO + serviceJob)

    private val _messagesMutableLiveData = MutableLiveData<Messages>()

    val messageLivedata: 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 = get().boxFor(Messages::class.java)
        username = getUsername()
        firstLoad()
    }

    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)
        }
    }

    fun sendMessage(messages: Messages) {
        updateConversationList(messages)
    }

    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 fun updateDbConversation(messages: Messages) {
        val conversation =
            conversationBox.query(Conversation_.talkerId.equal(messages.takerId)).build()
                .findFirst()

        val currentContactId = getCurrentContactId()

        conversation?.let {
            conversation.unreadCount =
                if (conversation.talkerId == currentContactId || messages.senderId == getUsername()) 0
                else conversation.unreadCount + 1

            conversation.lastContent = messages.content
            conversation.timestamp = messages.timestamp
            conversationBox.put(conversation)
        } ?: run {
            conversationBox.put(
                createChatList(
                    messages.takerId,
                    messages.takerId,
                    messages.content,
                    messages.timestamp
                )
            )
        }
    }

    private fun createChatList(
        talkerId: String,
        nickname: String,
        content: String,
        timestamp: Long
    ): Conversation {
        return Conversation(
            0L,
            talkerId,
            nickname,
            "https://s3.qjqq.cn/49/660ff4a698da0.webp!color",
            content,
            timestamp,
            1
        )
    }

    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)

            Log.d(TAG, "Message received: $messages")

            messages.takerId = messages.senderId

            updateDbConversation(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 {
                    _messagesMutableLiveData.postValue(messages)
                    messagesBox.put(messages)
                    updateConversationList(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()
        }
    }

    private val _conversations = MutableLiveData<List<Conversation>?>()

    val conversations: LiveData<List<Conversation>?> get() = _conversations

    private var conversationList = mutableListOf<Conversation>()

    private fun updateConversationList(messages: Messages) {
        updateDbConversation(messages)
        val index = conversationList.indexOfFirst { it.talkerId == messages.takerId }
        if (index != -1) {
            if (messages.timestamp < conversationList[index].timestamp) {
                return
            }
            conversationList[index].apply {
                lastContent = messages.content
                timestamp = messages.timestamp
                unreadCount =
                    if (this.talkerId == getCurrentContactId() || messages.senderId == getUsername()) 0 else unreadCount + 1
            }
        } else {
            conversationList.add(
                createChatList(
                    talkerId = messages.takerId,
                    nickname = messages.takerId,
                    content = messages.content,
                    timestamp = messages.timestamp
                )
            )
        }
        _conversations.postValue(conversationList.sortedByDescending { it.timestamp })
    }

    private fun firstLoad() {
        conversationList = conversationBox.query(Conversation_.show.equal(true))
            .orderDesc(Conversation_.timestamp)
            .build()
            .find()
        _conversations.postValue(conversationList)
    }

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