package dev.meloda.fast.domain

import dev.meloda.fast.common.VkConstants
import dev.meloda.fast.common.extensions.asInt
import dev.meloda.fast.common.extensions.asLong
import dev.meloda.fast.common.extensions.listenValue
import dev.meloda.fast.common.extensions.toList
import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.data.processState
import dev.meloda.fast.logger.FastLogger
import dev.meloda.fast.model.ApiEvent
import dev.meloda.fast.model.ConvoFlags
import dev.meloda.fast.model.InteractionType
import dev.meloda.fast.model.LongPollParsedEvent
import dev.meloda.fast.model.MessageFlags
import dev.meloda.fast.model.api.domain.VkConvo
import dev.meloda.fast.model.api.domain.VkMessage
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume

class LongPollUpdatesParser(
    private val logger: FastLogger,
    private val convoUseCase: ConvoUseCase,
    private val messagesUseCase: MessagesUseCase
) {
    private val job = SupervisorJob()

    private val exceptionHandler =
        CoroutineExceptionHandler { _, throwable ->
            logger.error(this::class, "CoroutineException", throwable)
        }

    private val coroutineContext: CoroutineContext
        get() = Dispatchers.Default + job + exceptionHandler

    private val coroutineScope = CoroutineScope(coroutineContext)

    suspend fun parseNextUpdate(event: List<Any>): List<LongPollParsedEvent> {
        val eventId = event.first().asInt()

        return when (val eventType = ApiEvent.parseOrNull(eventId)) {
            null -> {
                logger.debug(this::class, "parseNextUpdate(): unknownEvent: $event")
                emptyList()
            }

            ApiEvent.MESSAGE_SET_FLAGS -> parseMessageSetFlags(eventType, event)
            ApiEvent.MESSAGE_CLEAR_FLAGS -> parseMessageClearFlags(eventType, event)
            ApiEvent.MESSAGE_NEW -> parseMessageNew(eventType, event)
            ApiEvent.MESSAGE_EDIT -> parseMessageEdit(eventType, event)
            ApiEvent.MESSAGE_READ_INCOMING -> parseMessageReadIncoming(eventType, event)
            ApiEvent.MESSAGE_READ_OUTGOING -> parseMessageReadOutgoing(eventType, event)
            ApiEvent.CHAT_CLEAR_FLAGS -> parseChatClearFlags(eventType, event)
            ApiEvent.CHAT_SET_FLAGS -> parseChatSetFlags(eventType, event)
            ApiEvent.MESSAGES_DELETED -> parseMessagesDeleted(eventType, event)
            ApiEvent.CHAT_MAJOR_CHANGED -> parseChatMajorChanged(eventType, event)
            ApiEvent.CHAT_MINOR_CHANGED -> parseChatMinorChanged(eventType, event)

            ApiEvent.TYPING,
            ApiEvent.AUDIO_MESSAGE_RECORDING,
            ApiEvent.PHOTO_UPLOADING,
            ApiEvent.VIDEO_UPLOADING,
            ApiEvent.FILE_UPLOADING -> parseInteraction(eventType, event)

            ApiEvent.UNREAD_COUNT_UPDATE -> parseUnreadCounterUpdate(eventType, event)
            ApiEvent.MESSAGE_UPDATED -> parseMessageUpdated(eventType, event)
            ApiEvent.MESSAGE_CACHE_CLEAR -> parseMessageCacheClear(eventType, event)
        }
    }

    private fun parseMessageSetFlags(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> {
        logger.debug(this::class, "parseMessageSetFlags(): $eventType: $event")

        val cmId = event[1].asLong()
        val flags = event[2].asInt()
        val peerId = event[3].asLong()

        val eventsToSend = mutableListOf<LongPollParsedEvent>()

        val parsedFlags = MessageFlags.parse(flags)
        parsedFlags.forEach { flag ->
            when (flag) {
                MessageFlags.IMPORTANT -> {
                    val eventToSend = LongPollParsedEvent.MessageMarkedAsImportant(
                        peerId = peerId,
                        cmId = cmId,
                        marked = true
                    )
                    eventsToSend += eventToSend
                }

                MessageFlags.SPAM -> {
                    val eventToSend = LongPollParsedEvent.MessageMarkedAsSpam(
                        peerId = peerId,
                        cmId = cmId
                    )
                    eventsToSend += eventToSend
                }

                MessageFlags.DELETED -> {
                    val eventToSend =
                        if (parsedFlags.contains(MessageFlags.DELETED_FOR_ALL)) {
                            LongPollParsedEvent.MessageDeleted(
                                peerId = peerId,
                                cmId = cmId,
                                forAll = true
                            )
                        } else {
                            LongPollParsedEvent.MessageDeleted(
                                peerId = peerId,
                                cmId = cmId,
                                forAll = false
                            )
                        }
                    eventsToSend += eventToSend
                }

                MessageFlags.AUDIO_LISTENED -> {
                    val eventToSend = LongPollParsedEvent.AudioMessageListened(
                        peerId = peerId,
                        cmId = cmId
                    )
                    eventsToSend += eventToSend
                }

                MessageFlags.UNREAD -> Unit
                MessageFlags.OUTGOING -> Unit
                MessageFlags.FROM_GROUP_CHAT -> Unit
                MessageFlags.CANCEL_SPAM -> Unit
                MessageFlags.DELETED_FOR_ALL -> Unit
                MessageFlags.DO_NOT_SHOW_NOTIFICATION -> Unit
                MessageFlags.MESSAGE_WITH_REPLY -> Unit
                MessageFlags.REACTION -> Unit
            }
        }

        return eventsToSend
    }

    private suspend fun parseMessageClearFlags(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
        logger.debug(this::class, "parseMessageClearFlags(): $eventType: $event")

        val cmId = event[1].asLong()
        val flags = event[2].asInt()
        val peerId = event[3].asLong()

        val eventsToSend = mutableListOf<LongPollParsedEvent>()

        val parsedFlags = MessageFlags.parse(flags)

        coroutineScope.launch(Dispatchers.IO) {
            val message = loadMessage(peerId = peerId, cmId = cmId)

            parsedFlags.forEach { flag ->
                when (flag) {
                    MessageFlags.IMPORTANT -> {
                        val eventToSend = LongPollParsedEvent.MessageMarkedAsImportant(
                            peerId = peerId,
                            cmId = cmId,
                            marked = false
                        )
                        eventsToSend += eventToSend
                    }

                    MessageFlags.SPAM -> {
                        if (parsedFlags.contains(MessageFlags.CANCEL_SPAM)) {
                            if (message != null) {
                                val eventToSend =
                                    LongPollParsedEvent.MessageMarkedAsNotSpam(message = message)
                                eventsToSend += eventToSend
                            }
                        }
                    }

                    MessageFlags.DELETED -> {
                        if (message != null) {
                            val eventToSend =
                                LongPollParsedEvent.MessageRestored(message = message)
                            eventsToSend += eventToSend
                        }
                    }

                    MessageFlags.UNREAD -> Unit
                    MessageFlags.OUTGOING -> Unit
                    MessageFlags.AUDIO_LISTENED -> Unit
                    MessageFlags.FROM_GROUP_CHAT -> Unit
                    MessageFlags.CANCEL_SPAM -> Unit
                    MessageFlags.DELETED_FOR_ALL -> Unit
                    MessageFlags.DO_NOT_SHOW_NOTIFICATION -> Unit
                    MessageFlags.MESSAGE_WITH_REPLY -> Unit
                    MessageFlags.REACTION -> Unit
                }
            }

            continuation.resume(eventsToSend)
        }
    }

    private suspend fun parseMessageNew(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
        logger.debug(this::class, "parseMessageNew(): $eventType: $event")

        val cmId = event[1].asLong()
        val peerId = event[4].asLong()

        coroutineScope.launch(Dispatchers.IO) {
            val message = async { loadMessage(peerId = peerId, cmId = cmId) }.await()

            val convo =
                async {
                    loadConvo(
                        peerId = peerId,
                        extended = true,
                        fields = VkConstants.ALL_FIELDS
                    )
                }.await()

            if (message != null) {
                val event = LongPollParsedEvent.MessageNew(
                    message = message,
                    inArchive = convo?.isArchived == true
                    // TODO: 03-Apr-25, Danil Nikolaev:
                    // load user settings about restoring chats with
                    // enabled notifications from archive
                )

                continuation.resume(listOf(event))
            } else {
                continuation.resume(emptyList())
            }
        }
    }

    private suspend fun parseMessageEdit(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
        logger.debug(this::class, "parseMessageEdit(): $eventType: $event")

        val cmId = event[1].asLong()
        val peerId = event[3].asLong()

        coroutineScope.launch(Dispatchers.IO) {
            val message = loadMessage(peerId = peerId, cmId = cmId)
            if (message != null) {
                val event = LongPollParsedEvent.MessageEdited(message)
                continuation.resume(listOf(event))
            } else {
                continuation.resume(emptyList())
            }
        }
    }

    private fun parseMessageReadIncoming(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> {
        logger.debug(this::class, "parseMessageReadIncoming(): $eventType: $event")

        val peerId = event[1].asLong()
        val cmId = event[2].asLong()
        val unreadCount = event[3].asInt()

        val event = LongPollParsedEvent.IncomingMessageRead(
            peerId = peerId,
            cmId = cmId,
            unreadCount = unreadCount
        )
        return listOf(event)
    }

    private fun parseMessageReadOutgoing(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> {
        logger.debug(this::class, "parseMessageReadOutgoing(): $eventType: $event")

        val peerId = event[1].asLong()
        val cmId = event[2].asLong()
        val unreadCount = event[3].asInt()

        val event = LongPollParsedEvent.OutgoingMessageRead(
            peerId = peerId,
            cmId = cmId,
            unreadCount = unreadCount
        )

        return listOf(event)
    }

    private suspend fun parseChatClearFlags(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
        logger.debug(this::class, "parseChatClearFlags(): $eventType: $event")

        val peerId = event[1].asLong()
        val flags = event[2].asInt()

        val eventsToSend = mutableListOf<LongPollParsedEvent>()

        val parsedFlags = ConvoFlags.parse(flags)

        coroutineScope.launch(Dispatchers.IO) {
            parsedFlags.forEach { flag ->
                when (flag) {
                    ConvoFlags.ARCHIVED -> {
                        val convo = loadConvo(
                            peerId = peerId,
                            extended = true,
                            fields = VkConstants.ALL_FIELDS
                        ) ?: return@forEach

                        val message = loadMessage(
                            peerId = peerId,
                            cmId = convo.lastCmId
                        )

                        val eventToSend = LongPollParsedEvent.ChatArchived(
                            convo = convo.copy(lastMessage = message),
                            archived = false
                        )
                        eventsToSend += eventToSend
                    }

                    ConvoFlags.DISABLE_PUSH -> Unit
                    ConvoFlags.DISABLE_SOUND -> Unit
                    ConvoFlags.INCOMING_CHAT_REQUEST -> Unit
                    ConvoFlags.DECLINED_CHAT_REQUEST -> Unit
                    ConvoFlags.MENTION -> Unit
                    ConvoFlags.HIDE_CHAT_FROM_SEARCH -> Unit
                    ConvoFlags.BUSINESS_CHAT -> Unit
                    ConvoFlags.MARKED_MESSAGE -> Unit
                    ConvoFlags.DO_NOT_NOTIFY_MENTIONS_ALL_ONLINE -> Unit
                    ConvoFlags.DO_NOT_NOTIFY_ALL_MENTIONS -> Unit
                    ConvoFlags.MARKED_AS_UNREAD -> Unit
                    ConvoFlags.CALL_IN_PROGRESS -> Unit
                }
            }

            continuation.resume(eventsToSend)
        }
    }

    private suspend fun parseChatSetFlags(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
        logger.debug(this::class, "parseChatSetFlags(): $eventType: $event")

        val peerId = event[1].asLong()
        val flags = event[2].asInt()

        val eventsToSend = mutableListOf<LongPollParsedEvent>()

        val parsedFlags = ConvoFlags.parse(flags)

        coroutineScope.launch(Dispatchers.IO) {
            parsedFlags.forEach { flag ->
                when (flag) {
                    ConvoFlags.ARCHIVED -> {
                        val convo = loadConvo(
                            peerId = peerId,
                            extended = true,
                            fields = VkConstants.ALL_FIELDS
                        ) ?: return@forEach

                        val message = loadMessage(
                            peerId = peerId,
                            cmId = convo.lastCmId
                        )

                        val eventToSend = LongPollParsedEvent.ChatArchived(
                            convo = convo.copy(lastMessage = message),
                            archived = true
                        )
                        eventsToSend += eventToSend
                    }

                    ConvoFlags.DISABLE_PUSH -> Unit
                    ConvoFlags.DISABLE_SOUND -> Unit
                    ConvoFlags.INCOMING_CHAT_REQUEST -> Unit
                    ConvoFlags.DECLINED_CHAT_REQUEST -> Unit
                    ConvoFlags.MENTION -> Unit
                    ConvoFlags.HIDE_CHAT_FROM_SEARCH -> Unit
                    ConvoFlags.BUSINESS_CHAT -> Unit
                    ConvoFlags.MARKED_MESSAGE -> Unit
                    ConvoFlags.DO_NOT_NOTIFY_MENTIONS_ALL_ONLINE -> Unit
                    ConvoFlags.DO_NOT_NOTIFY_ALL_MENTIONS -> Unit
                    ConvoFlags.MARKED_AS_UNREAD -> Unit
                    ConvoFlags.CALL_IN_PROGRESS -> Unit
                }
            }

            continuation.resume(eventsToSend)
        }
    }

    private fun parseMessagesDeleted(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> {
        logger.debug(this::class, "parseMessagesDeleted(): $eventType: $event")

        val peerId = event[1].asLong()
        val cmId = event[2].asLong()

        val event = LongPollParsedEvent.ChatCleared(
            peerId = peerId,
            toCmId = cmId
        )
        return listOf(event)
    }

    private fun parseChatMajorChanged(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> {
        logger.debug(this::class, "parseChatMajorChanged(): $eventType: $event")

        val peerId = event[1].asLong()
        val majorId = event[2].asInt()

        val event = LongPollParsedEvent.ChatMajorChanged(
            peerId = peerId,
            majorId = majorId,
        )
        return listOf(event)
    }

    private fun parseChatMinorChanged(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> {
        logger.debug(this::class, "parseChatMinorChanged(): $eventType: $event")

        val peerId = event[1].asLong()
        val minorId = event[2].asInt()

        val event = LongPollParsedEvent.ChatMinorChanged(
            peerId = peerId,
            minorId = minorId,
        )
        return listOf(event)
    }

    private fun parseInteraction(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> {
        logger.debug(this::class, "parseInteraction(): $eventType: $event")

        val interactionType = when (eventType) {
            ApiEvent.TYPING -> InteractionType.Typing
            ApiEvent.AUDIO_MESSAGE_RECORDING -> InteractionType.VoiceMessage
            ApiEvent.PHOTO_UPLOADING -> InteractionType.Photo
            ApiEvent.VIDEO_UPLOADING -> InteractionType.Video
            ApiEvent.FILE_UPLOADING -> InteractionType.File
            else -> return emptyList()
        }

        val peerId = event[1].asLong()
        val userIds = event[2].toList(Any::asLong).filter { it != UserConfig.userId }
        val totalCount = event[3].asInt()
        val timestamp = event[4].asInt()

        // if userIds contains only account's id, then we don't need to show our status
        if (userIds.isEmpty()) return emptyList()

        val event = LongPollParsedEvent.Interaction(
            interactionType = interactionType,
            peerId = peerId,
            userIds = userIds,
            totalCount = totalCount,
            timestamp = timestamp
        )

        return listOf(event)
    }

    private fun parseUnreadCounterUpdate(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> {
        logger.debug(this::class, "parseUnreadCounterUpdate(): $eventType: $event")

        val unreadCount = event[1].asInt()
        val unreadUnmutedCount = event[2].asInt()
        val showOnlyMuted = event[3].asInt() == 1
        val businessNotifyUnreadCount = event[4].asInt()
        val archiveUnreadCount = event[7].asInt()
        val archiveUnreadUnmutedCount = event[8].asInt()
        val archiveMentionsCount = event[9].asInt()

        val event = LongPollParsedEvent.UnreadCounter(
            unread = unreadCount,
            unreadUnmuted = unreadUnmutedCount,
            showOnlyMuted = showOnlyMuted,
            business = businessNotifyUnreadCount,
            archive = archiveUnreadCount,
            archiveUnmuted = archiveUnreadUnmutedCount,
            archiveMentions = archiveMentionsCount
        )
        return listOf(event)
    }

    private suspend fun parseMessageUpdated(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
        logger.debug(this::class, "parseMessageUpdated(): $eventType: $event")

        val cmId = event[1].asLong()
        val peerId = event[4].asLong()

        coroutineScope.launch(Dispatchers.IO) {
            val message = loadMessage(peerId = peerId, cmId = cmId)

            if (message != null) {
                val event = LongPollParsedEvent.MessageUpdated(message)
                continuation.resume(listOf(event))
            } else {
                continuation.resume(emptyList())
            }
        }
    }

    private suspend fun parseMessageCacheClear(
        eventType: ApiEvent,
        event: List<Any>
    ): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
        logger.debug(this::class, "parseMessageCacheClear(): $eventType: $event")

        val messageId = event[1].asLong()

        coroutineScope.launch(Dispatchers.IO) {
            val message = loadMessage(messageId = messageId)
            if (message != null) {
                val event = LongPollParsedEvent.MessageCacheClear(message)
                continuation.resume(listOf(event))
            } else {
                continuation.resume(emptyList())
            }
        }
    }

    private suspend fun loadMessage(
        peerId: Long? = null,
        cmId: Long? = null,
        messageId: Long? = null
    ): VkMessage? = suspendCancellableCoroutine { continuation ->
        require((peerId != null && cmId != null) || messageId != null)

        coroutineScope.launch(Dispatchers.IO) {
            messagesUseCase.getById(
                peerCmIds = null,
                peerId = peerId,
                messageIds = messageId?.let(::listOf),
                cmIds = cmId?.let(::listOf),
                extended = true,
                fields = VkConstants.ALL_FIELDS
            ).listenValue(this) { state ->
                state.processState(
                    error = { error ->
                        logger.error(
                            this@LongPollUpdatesParser::class,
                            "loadMessage(): ERROR: $error"
                        )
                        continuation.resume(null)
                    },
                    success = { response ->
                        val message = response.singleOrNull() ?: run {
                            continuation.resume(null)
                            return@listenValue
                        }

                        continuation.resume(message)
                    }
                )
            }
        }
    }

    private suspend fun loadConvo(
        peerId: Long,
        extended: Boolean = false,
        fields: String? = null
    ): VkConvo? = suspendCancellableCoroutine { continuation ->
        coroutineScope.launch(Dispatchers.IO) {
            convoUseCase.getById(
                peerIds = listOf(peerId),
                extended = extended,
                fields = fields
            ).listenValue(coroutineScope) { state ->
                state.processState(
                    error = { error ->
                        logger.error(
                            this@LongPollUpdatesParser::class,
                            "loadConvo(): ERROR: $error"
                        )
                        continuation.resume(null)
                    },
                    success = { response ->
                        val convo = response.singleOrNull() ?: run {
                            continuation.resume(null)
                            return@listenValue
                        }

                        continuation.resume(convo)
                    }
                )
            }
        }
    }
}
