package dev.meloda.fast.messageshistory

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextDecoration
import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.toBitmapOrNull
import coil.imageLoader
import coil.request.ImageRequest
import com.conena.nanokt.collections.indexOfFirstOrNull
import dev.meloda.fast.common.extensions.listenValue
import dev.meloda.fast.common.extensions.orDots
import dev.meloda.fast.common.extensions.removeIfCompat
import dev.meloda.fast.common.extensions.setValue
import dev.meloda.fast.common.provider.ResourceProvider
import dev.meloda.fast.data.State
import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.data.VkMemoryCache
import dev.meloda.fast.data.VkUtils
import dev.meloda.fast.data.processState
import dev.meloda.fast.domain.MessagesUseCase
import dev.meloda.fast.domain.util.extractReplySummary
import dev.meloda.fast.domain.util.extractReplyTitle
import dev.meloda.fast.domain.util.extractTitle
import dev.meloda.fast.messageshistory.model.ActionMode
import dev.meloda.fast.messageshistory.model.MessageDialog
import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.model.api.domain.FormatDataType
import dev.meloda.fast.model.api.domain.VkMessage
import dev.meloda.fast.model.api.domain.VkPhotoDomain
import dev.meloda.fast.ui.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.add
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import java.io.File
import java.io.FileOutputStream
import kotlin.random.Random

internal class MessagesHistoryMessageActions(
    private val applicationContext: Context,
    private val viewModelScope: CoroutineScope,
    private val messagesUseCase: MessagesUseCase,
    private val resourceProvider: ResourceProvider,
    private val screenState: MutableStateFlow<MessagesHistoryScreenState>,
    private val messages: MutableStateFlow<List<VkMessage>>,
    private val baseError: MutableStateFlow<BaseError?>,
    private val showKeyboard: MutableStateFlow<Boolean>,
    private val dialog: MutableStateFlow<MessageDialog?>,
    private val syncUiMessages: () -> Unit,
    private val onPinnedMessageChanged: (VkMessage?) -> Unit
) {
    private var lastMessageText: String? = null
    private val sendingMessages: MutableList<VkMessage> = mutableListOf()
    private val failedMessages: MutableList<VkMessage> = mutableListOf()
    private var replyToCmId: Long? = null
    private var editMessage: VkMessage? = null
    private var formatData = VkMessage.FormatData("1", emptyList())

    fun replyToMessage(cmId: Long) {
        val messageToReply = messages.value.find { it.cmId == cmId } ?: return

        showKeyboard.setValue { true }
        replyToCmId = cmId
        screenState.setValue { old ->
            old.copy(
                replyTitle = messageToReply.extractTitle(),
                replyText = messageToReply.extractReplySummary(resourceProvider.resources)
            )
        }
    }

    fun editMessage(cmId: Long) {
        screenState.setValue { old -> old.copy(editCmId = cmId) }

        val messageToEdit = messages.value.firstOrNull { it.cmId == cmId } ?: return
        editMessage = messageToEdit

        lastMessageText = screenState.value.message.text

        var newState = screenState.value.copy(
            message = TextFieldValue(
                text = messageToEdit.text.orEmpty(),
                selection = TextRange(messageToEdit.text.orEmpty().length)
            ),
            actionMode = ActionMode.EDIT
        )

        messageToEdit.replyMessage?.let { reply ->
            replyToCmId = reply.cmId
            newState = newState.copy(
                replyTitle = reply.extractReplyTitle(),
                replyText = reply.extractReplySummary(resourceProvider.resources)
            )
        }

        showKeyboard.setValue { true }
        screenState.setValue { newState }
    }

    fun stopEditMessage() {
        val lastText = lastMessageText.orEmpty().trim()

        screenState.setValue { old ->
            old.copy(
                editCmId = null,
                message = TextFieldValue(
                    text = lastText,
                    selection = TextRange(lastText.length)
                ),
                actionMode = if (lastText.isBlank()) ActionMode.RECORD_AUDIO else ActionMode.SEND,
                replyTitle = null,
                replyText = null
            )
        }
    }

    fun onBoldClicked() = updateFormatting(FormatDataType.BOLD)
    fun onItalicClicked() = updateFormatting(FormatDataType.ITALIC)
    fun onUnderlineClicked() = updateFormatting(FormatDataType.UNDERLINE)

    fun onRegularClicked() {
        formatData = formatData.copy(items = emptyList())
        updateStyles()
    }

    fun onReplyCloseClicked() {
        replyToCmId = null
        screenState.setValue { old ->
            old.copy(
                replyTitle = null,
                replyText = null
            )
        }
    }

    fun sendMessage() {
        lastMessageText = screenState.value.message.text

        val newMessage = VkMessage(
            id = -1L - sendingMessages.size,
            cmId = -1L - sendingMessages.size,
            text = lastMessageText,
            isOut = true,
            peerId = screenState.value.convoId,
            fromId = UserConfig.userId,
            date = (System.currentTimeMillis() / 1000).toInt(),
            randomId = Random.nextInt().toLong(),
            action = null,
            actionMemberId = null,
            actionText = null,
            actionCmId = null,
            actionMessage = null,
            updateTime = null,
            isImportant = false,
            forwards = null,
            attachments = null,
            replyMessage = when {
                replyToCmId != null -> messages.value.find { it.cmId == replyToCmId }
                else -> null
            },
            geoType = null,
            user = VkMemoryCache.getUser(UserConfig.userId),
            group = null,
            actionUser = null,
            actionGroup = null,
            isPinned = false,
            isSpam = false,
            pinnedAt = null,
            formatData = formatData,
        )
        formatData = formatData.copy(items = emptyList())
        sendingMessages += newMessage
        messages.setValue { old -> listOf(newMessage).plus(old) }
        syncUiMessages()

        screenState.setValue { old ->
            old.copy(
                message = TextFieldValue(),
                actionMode = ActionMode.RECORD_AUDIO,
                replyTitle = null,
                replyText = null
            )
        }

        val replyCmId = replyToCmId
        replyToCmId = null

        val forward = when {
            replyCmId != null -> {
                buildJsonObject {
                    put("peer_id", screenState.value.convoId)
                    put("conversation_message_ids", buildJsonArray { add(replyCmId) })
                    put("is_reply", true)
                }.toString()
            }

            else -> null
        }

        messagesUseCase.sendMessage(
            peerId = screenState.value.convoId,
            randomId = newMessage.randomId,
            message = newMessage.text,
            forward = forward,
            attachments = null,
            formatData = newMessage.formatData,
        ).listenValue(viewModelScope) { state ->
            state.processState(
                any = { sendingMessages.remove(newMessage) },
                error = { error ->
                    Log.d("MessagesHistoryViewModelImpl", "sendMessage: ERROR: $error")

                    val failedId = -500_000L - failedMessages.size
                    val newFailedMessage = newMessage.copy(id = failedId)
                    failedMessages += newFailedMessage

                    val newMessages = messages.value.toMutableList()
                    newMessages[newMessages.indexOf(newMessage)] = newFailedMessage
                    messages.setValue { newMessages }
                    syncUiMessages()
                },
                success = { response ->
                    val newMessages = messages.value.toMutableList()
                    newMessages[newMessages.indexOf(newMessage)] = newMessage.copy(
                        id = response.messageId,
                        cmId = response.cmId
                    )
                    messages.setValue { newMessages }
                    syncUiMessages()
                }
            )
        }
    }

    fun confirmDeleteCurrentEditMessage() {
        val currentMessage = editMessage ?: return
        dialog.setValue { MessageDialog.MessageDelete(currentMessage) }
    }

    fun editCurrentEditMessage() {
        replyToCmId = null

        val newText = screenState.value.message.text
        val lastText = lastMessageText.orEmpty().trim()

        screenState.setValue { old ->
            old.copy(
                editCmId = null,
                message = TextFieldValue(
                    text = lastText,
                    selection = TextRange(lastText.length)
                ),
                actionMode = if (lastText.isBlank()) ActionMode.RECORD_AUDIO else ActionMode.SEND,
                replyTitle = null,
                replyText = null
            )
        }

        syncUiMessages()

        val newMessage = editMessage?.copy(
            replyMessage = if (replyToCmId == null) null else editMessage?.replyMessage,
            text = newText
        ) ?: return

        Log.d("MessagesHistoryViewModelImpl", "editMessage: $newMessage")
    }

    fun markAsImportant(messageIds: List<Long>, important: Boolean) {
        messagesUseCase.markAsImportant(
            peerId = screenState.value.convoId,
            messageIds = messageIds,
            important = important
        ).listenValue(viewModelScope) { state ->
            state.processState(
                error = ::handleError,
                success = {
                    val newMessages = messages.value
                        .toMutableList()
                        .map { message ->
                            if (message.id in messageIds) {
                                message.copy(isImportant = important)
                            } else {
                                message
                            }
                        }
                    messages.setValue { newMessages }
                    syncUiMessages()
                }
            )
        }
    }

    fun deleteMessage(
        messageIds: List<Long>,
        spam: Boolean = false,
        deleteForAll: Boolean = false,
        onSuccess: () -> Unit = {}
    ) {
        messagesUseCase.delete(
            peerId = screenState.value.convoId,
            messageIds = messageIds,
            spam = spam,
            deleteForAll = deleteForAll
        ).listenValue(viewModelScope) { state ->
            state.processState(
                error = ::handleError,
                success = {
                    onSuccess()
                    val newMessages = messages.value.toMutableList()
                    val messagesToDelete = newMessages.filter { it.id in messageIds }
                    newMessages.removeAll(messagesToDelete)
                    messages.setValue { newMessages }
                    syncUiMessages()
                }
            )
        }
    }

    fun pinMessage(messageId: Long) {
        messagesUseCase.pin(
            peerId = screenState.value.convoId,
            messageId = messageId,
            cmId = null
        ).listenValue(viewModelScope) { state ->
            state.processState(
                error = ::handleError,
                success = { pinnedMessage ->
                    onPinnedMessageChanged(pinnedMessage)

                    val newMessages = messages.value.toMutableList()
                    val index = newMessages.indexOfFirstOrNull { it.id == messageId }
                    if (index != null) {
                        newMessages[index] = pinnedMessage
                        messages.setValue { newMessages }
                        syncUiMessages()
                    }
                }
            )
        }
    }

    fun unpinMessage(messageId: Long) {
        messagesUseCase.unpin(screenState.value.convoId)
            .listenValue(viewModelScope) { state ->
                state.processState(
                    error = ::handleError,
                    success = {
                        val newMessages = messages.value.toMutableList()
                        val index = newMessages.indexOfFirstOrNull { it.id == messageId }
                        if (index != null) {
                            newMessages[index] = newMessages[index].copy(isPinned = false)
                            messages.setValue { newMessages }
                            syncUiMessages()
                        }

                        onPinnedMessageChanged(null)
                    }
                )
            }
    }

    fun readMessage(message: VkMessage) {
        messagesUseCase.markAsRead(
            peerId = screenState.value.convoId,
            startMessageId = message.id
        ).listenValue(viewModelScope) { state ->
            state.processState(
                error = ::handleError,
                success = {
                    val oldConvo = screenState.value.convo
                    val newConvo = oldConvo.copy(
                        inRead = if (!message.isOut) message.id else oldConvo.inRead,
                        outRead = if (message.isOut) message.id else oldConvo.outRead
                    )

                    screenState.setValue { old ->
                        old.copy(convo = newConvo)
                    }

                    syncUiMessages()
                }
            )
        }
    }

    fun copyMessage(message: VkMessage) {
        val clipboardManager =
            applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager

        val messageToCopy = message.text.orEmpty().trim()
        if (messageToCopy.isEmpty()) {
            val photo = with(message.attachments.orEmpty()) {
                if (size == 1 && all { it is VkPhotoDomain }) {
                    first() as? VkPhotoDomain
                } else null
            } ?: return

            val photoMaxSize = photo.getMaxSize() ?: return

            viewModelScope.launch(Dispatchers.IO) {
                val drawable = applicationContext.imageLoader.execute(
                    ImageRequest.Builder(applicationContext)
                        .data(photoMaxSize.url)
                        .build()
                ).drawable ?: return@launch

                val imagesDir = File(applicationContext.cacheDir, "images")
                if (!imagesDir.exists()) imagesDir.mkdirs()
                val imageFile = File(imagesDir, "shared_image_id${photo.id}.png")
                FileOutputStream(imageFile).use {
                    drawable.toBitmapOrNull()?.compress(Bitmap.CompressFormat.PNG, 100, it)
                }

                val uri = FileProvider.getUriForFile(
                    applicationContext,
                    "${applicationContext.packageName}.provider",
                    imageFile
                )

                val clip = ClipData.newUri(applicationContext.contentResolver, "Image", uri)
                clipboardManager.setPrimaryClip(clip)

                withContext(Dispatchers.Main) {
                    Toast.makeText(
                        applicationContext,
                        "Image copied to clipboard",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            return
        }

        clipboardManager.setPrimaryClip(ClipData.newPlainText("Message", messageToCopy))

        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) {
            Toast.makeText(applicationContext, R.string.copied_to_clipboard, Toast.LENGTH_SHORT)
                .show()
        }
    }

    private fun updateFormatting(type: FormatDataType) {
        val selectionRange = screenState.value.message.selection
        val newItems = formatData.items.toMutableList()
        val wasRemoved = newItems.removeIfCompat {
            it.type == type &&
                it.offset == selectionRange.start &&
                it.offset + it.length == selectionRange.end
        }

        if (!wasRemoved) {
            newItems += VkMessage.FormatData.Item(
                offset = selectionRange.start,
                length = selectionRange.end - selectionRange.start,
                type = type,
                url = null
            )
        }

        formatData = formatData.copy(items = newItems)
        updateStyles()
    }

    private fun updateStyles() {
        val annotations =
            mutableListOf<AnnotatedString.Range<out AnnotatedString.Annotation>>()

        formatData.items.forEach { item ->
            val spanStyle = when (item.type) {
                FormatDataType.BOLD -> SpanStyle(fontWeight = FontWeight.SemiBold)
                FormatDataType.ITALIC -> SpanStyle(fontStyle = FontStyle.Italic)
                FormatDataType.UNDERLINE -> SpanStyle(textDecoration = TextDecoration.Underline)
                FormatDataType.URL -> null
            }

            spanStyle?.let {
                annotations += AnnotatedString.Range(
                    item = spanStyle,
                    start = item.offset,
                    end = item.offset + item.length
                )
            }
        }

        val newText = AnnotatedString(
            text = screenState.value.message.text,
            annotations = annotations
        )

        screenState.setValue { old ->
            old.copy(message = old.message.copy(annotatedString = newText))
        }
    }

    private fun handleError(error: State.Error) {
        VkUtils.parseError(error)?.let { newBaseError ->
            baseError.setValue { newBaseError }
        }
    }
}
