package com.rosetta.messenger.ui.chats

import com.rosetta.messenger.crypto.CryptoManager
import com.rosetta.messenger.data.DraftManager
import com.rosetta.messenger.domain.chats.usecase.SendTextMessageCommand
import com.rosetta.messenger.network.AttachmentType
import com.rosetta.messenger.network.DeliveryStatus
import com.rosetta.messenger.network.MessageAttachment
import com.rosetta.messenger.ui.chats.models.ChatMessage
import com.rosetta.messenger.ui.chats.models.MessageStatus
import com.rosetta.messenger.ui.chats.models.ReplyData
import com.rosetta.messenger.utils.MessageThrottleManager
import java.util.Date
import java.util.UUID
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject

internal class MessagesCoordinator(
    private val chatViewModel: ChatViewModel
) {
    private var pendingTextSendRequested = false
    private var pendingTextSendReason: String = ""
    private var pendingSendRecoveryJob: Job? = null

    private data class SendCommand(
        val messageId: String,
        val timestamp: Long,
        val text: String,
        val replyMessages: List<ChatViewModel.ReplyMessage>,
        val isForward: Boolean,
        val senderPublicKey: String,
        val senderPrivateKey: String,
        val recipientPublicKey: String
    ) {
        val dialogThrottleKey: String
            get() = "$senderPublicKey:$recipientPublicKey"
    }

    fun onSendContextChanged(trigger: String) {
        triggerPendingTextSendIfReady(trigger)
    }

    fun onCleared() {
        clearPendingRecovery(cancelJob = true)
    }

    fun trySendMessage(allowPendingRecovery: Boolean) {
        val text = chatViewModel.inputText.value.trim()
        val replyMsgsToSend = chatViewModel.replyMessages.value.toList()
        val isForward = chatViewModel.isForwardMode.value
        val hasPayload = text.isNotEmpty() || replyMsgsToSend.isNotEmpty()

        if (!hasPayload) return

        if (!chatViewModel.hasRuntimeKeysForSend()) {
            recoverRuntimeKeysIfMissing()
        }

        val recipient = chatViewModel.currentRecipientForSend()
        val sender = chatViewModel.currentSenderPublicKeyForSend()
        val privateKey = chatViewModel.currentSenderPrivateKeyForSend()

        if (recipient == null) {
            logSendBlocked(
                reason = "no_dialog",
                textLength = text.length,
                hasReply = replyMsgsToSend.isNotEmpty(),
                recipient = null,
                sender = sender,
                hasPrivateKey = privateKey != null
            )
            if (allowPendingRecovery) {
                schedulePendingTextSendRecovery(reason = "no_dialog", hasPayload = hasPayload)
            }
            return
        }

        if (sender == null || privateKey == null) {
            logSendBlocked(
                reason = "no_keys",
                textLength = text.length,
                hasReply = replyMsgsToSend.isNotEmpty(),
                recipient = recipient,
                sender = sender,
                hasPrivateKey = privateKey != null
            )
            if (allowPendingRecovery) {
                schedulePendingTextSendRecovery(reason = "no_keys", hasPayload = hasPayload)
            }
            return
        }

        val command =
            SendCommand(
                messageId = UUID.randomUUID().toString().replace("-", "").take(32),
                timestamp = System.currentTimeMillis(),
                text = text,
                replyMessages = replyMsgsToSend,
                isForward = isForward,
                senderPublicKey = sender,
                senderPrivateKey = privateKey,
                recipientPublicKey = recipient
            )

        if (!MessageThrottleManager.canSendWithContent(command.dialogThrottleKey, command.text.hashCode())) {
            logSendBlocked(
                reason = "throttle",
                textLength = command.text.length,
                hasReply = command.replyMessages.isNotEmpty(),
                recipient = command.recipientPublicKey,
                sender = command.senderPublicKey,
                hasPrivateKey = true
            )
            return
        }

        val messageId = command.messageId
        val timestamp = command.timestamp
        val fallbackName = chatViewModel.replyFallbackName()
        val currentMessages = chatViewModel.messages.value

        val replyData: ReplyData? =
            if (command.replyMessages.isNotEmpty()) {
                val firstReply = command.replyMessages.first()
                val replyAttachments =
                    currentMessages.find { it.id == firstReply.messageId }?.attachments
                        ?: firstReply.attachments.filter { it.type != AttachmentType.MESSAGES }
                val firstReplySenderName =
                    if (firstReply.isOutgoing) {
                        "You"
                    } else {
                        firstReply.senderName.ifEmpty { fallbackName }
                    }

                ReplyData(
                    messageId = firstReply.messageId,
                    senderName = firstReplySenderName,
                    text = chatViewModel.resolveReplyPreviewTextForSend(firstReply.text, replyAttachments),
                    isFromMe = firstReply.isOutgoing,
                    isForwarded = command.isForward,
                    forwardedFromName = if (command.isForward) firstReplySenderName else "",
                    attachments = replyAttachments,
                    senderPublicKey =
                        firstReply.publicKey.ifEmpty {
                            if (firstReply.isOutgoing) command.senderPublicKey else command.recipientPublicKey
                        },
                    recipientPrivateKey = command.senderPrivateKey
                )
            } else {
                null
            }

        val optimisticForwardedMessages: List<ReplyData> =
            if (command.isForward && command.replyMessages.isNotEmpty()) {
                command.replyMessages.map { message ->
                    val senderDisplayName =
                        if (message.isOutgoing) {
                            "You"
                        } else {
                            message.senderName.ifEmpty { fallbackName }
                        }
                    val resolvedAttachments =
                        currentMessages.find { it.id == message.messageId }?.attachments
                            ?: message.attachments.filter { it.type != AttachmentType.MESSAGES }

                    ReplyData(
                        messageId = message.messageId,
                        senderName = senderDisplayName,
                        text = chatViewModel.resolveReplyPreviewTextForSend(message.text, resolvedAttachments),
                        isFromMe = message.isOutgoing,
                        isForwarded = true,
                        forwardedFromName = senderDisplayName,
                        attachments = resolvedAttachments,
                        senderPublicKey =
                            message.publicKey.ifEmpty {
                                if (message.isOutgoing) command.senderPublicKey
                                else command.recipientPublicKey
                            },
                        recipientPrivateKey = command.senderPrivateKey
                    )
                }
            } else {
                emptyList()
            }

        val isForwardToSend = command.isForward

        chatViewModel.addOutgoingMessageOptimistic(
            ChatMessage(
                id = messageId,
                text = command.text,
                isOutgoing = true,
                timestamp = Date(timestamp),
                status = MessageStatus.SENDING,
                replyData = if (isForwardToSend) null else replyData,
                forwardedMessages = optimisticForwardedMessages
            )
        )
        chatViewModel.clearInputText()
        DraftManager.clearDraft(command.recipientPublicKey)
        chatViewModel.clearReplyMessages()
        chatViewModel.cacheDecryptedText(messageId, command.text)

        chatViewModel.launchOnIo {
            try {
                val encryptionContext =
                    chatViewModel.buildEncryptionContext(
                        plaintext = command.text,
                        recipient = command.recipientPublicKey,
                        privateKey = command.senderPrivateKey
                    ) ?: throw IllegalStateException("Cannot resolve chat encryption context")
                val encryptedContent = encryptionContext.encryptedContent
                val encryptedKey = encryptionContext.encryptedKey
                val aesChachaKey = encryptionContext.aesChachaKey

                val privateKeyHash = CryptoManager.generatePrivateKeyHash(command.senderPrivateKey)
                val messageAttachments = mutableListOf<MessageAttachment>()
                var replyBlobForDatabase = ""

                val isSavedMessages = (command.senderPublicKey == command.recipientPublicKey)
                val (forwardedAttachments, rewrittenForwardMessageIds) =
                    if (isForwardToSend && command.replyMessages.isNotEmpty()) {
                        chatViewModel.prepareForwardAttachmentRewritesForTextSend(
                            sourceMessages = command.replyMessages,
                            encryptionContext = encryptionContext,
                            privateKey = command.senderPrivateKey,
                            isSavedMessages = isSavedMessages,
                            timestamp = timestamp
                        )
                    } else {
                        emptyMap<String, MessageAttachment>() to emptySet<String>()
                    }
                val outgoingForwardPlainKeyHex =
                    encryptionContext.plainKeyAndNonce
                        ?.joinToString("") { "%02x".format(it) }
                        .orEmpty()

                if (command.replyMessages.isNotEmpty()) {
                    val replyJsonArray = JSONArray()
                    command.replyMessages.forEach { message ->
                        val attachmentsArray = JSONArray()
                        message.attachments.forEach { attachment ->
                            val rewrittenAttachment =
                                forwardedAttachments[
                                    chatViewModel.forwardAttachmentRewriteKeyForTextSend(
                                        message.messageId,
                                        attachment.id
                                    )
                                ]
                            val attachmentId = rewrittenAttachment?.id ?: attachment.id
                            val attachmentPreview = rewrittenAttachment?.preview ?: attachment.preview
                            val attachmentTransportTag =
                                rewrittenAttachment?.transportTag ?: attachment.transportTag
                            val attachmentTransportServer =
                                rewrittenAttachment?.transportServer ?: attachment.transportServer

                            attachmentsArray.put(
                                JSONObject().apply {
                                    put("id", attachmentId)
                                    put("type", attachment.type.value)
                                    put("preview", attachmentPreview)
                                    put("width", attachment.width)
                                    put("height", attachment.height)
                                    put(
                                        "blob",
                                        if (attachment.type == AttachmentType.MESSAGES) attachment.blob else ""
                                    )
                                    put("transportTag", attachmentTransportTag)
                                    put("transportServer", attachmentTransportServer)
                                    put(
                                        "transport",
                                        JSONObject().apply {
                                            put("transport_tag", attachmentTransportTag)
                                            put("transport_server", attachmentTransportServer)
                                        }
                                    )
                                }
                            )
                        }

                        val replyJson =
                            JSONObject().apply {
                                put("message_id", message.messageId)
                                put("publicKey", message.publicKey)
                                put("message", message.text)
                                put("timestamp", message.timestamp)
                                put("attachments", attachmentsArray)
                                if (isForwardToSend) {
                                    put("forwarded", true)
                                    put("senderName", message.senderName)
                                    val effectiveForwardPlainKey =
                                        if (message.messageId in rewrittenForwardMessageIds &&
                                            outgoingForwardPlainKeyHex.isNotEmpty()
                                        ) {
                                            outgoingForwardPlainKeyHex
                                        } else {
                                            message.chachaKeyPlainHex
                                        }
                                    if (effectiveForwardPlainKey.isNotEmpty()) {
                                        put("chacha_key_plain", effectiveForwardPlainKey)
                                    }
                                }
                            }
                        replyJsonArray.put(replyJson)
                    }

                    val replyBlobPlaintext = replyJsonArray.toString()
                    val encryptedReplyBlob =
                        chatViewModel.encryptAttachmentPayloadForTextSend(
                            payload = replyBlobPlaintext,
                            context = encryptionContext
                        )
                    replyBlobForDatabase =
                        CryptoManager.encryptWithPassword(replyBlobPlaintext, command.senderPrivateKey)

                    val replyAttachmentId = "reply_$timestamp"
                    messageAttachments.add(
                        MessageAttachment(
                            id = replyAttachmentId,
                            blob = encryptedReplyBlob,
                            type = AttachmentType.MESSAGES,
                            preview = ""
                        )
                    )
                }

                chatViewModel.sendTextMessage(
                    SendTextMessageCommand(
                        fromPublicKey = command.senderPublicKey,
                        toPublicKey = command.recipientPublicKey,
                        encryptedContent = encryptedContent,
                        encryptedKey = encryptedKey,
                        aesChachaKey = aesChachaKey,
                        privateKeyHash = privateKeyHash,
                        messageId = messageId,
                        timestamp = timestamp,
                        attachments = messageAttachments,
                        isSavedMessages = isSavedMessages
                    )
                )

                withContext(Dispatchers.Main) {
                    if (isSavedMessages) {
                        chatViewModel.updateMessageStatusUi(messageId, MessageStatus.SENT)
                    }
                }

                val attachmentsJson =
                    if (messageAttachments.isNotEmpty()) {
                        JSONArray()
                            .apply {
                                messageAttachments.forEach { attachment ->
                                    put(
                                        JSONObject().apply {
                                            put("id", attachment.id)
                                            put("type", attachment.type.value)
                                            put("preview", attachment.preview)
                                            put("width", attachment.width)
                                            put("height", attachment.height)
                                            put("transportTag", attachment.transportTag)
                                            put("transportServer", attachment.transportServer)
                                            val blobToSave =
                                                when (attachment.type) {
                                                    AttachmentType.MESSAGES -> replyBlobForDatabase
                                                    else -> ""
                                                }
                                            put("blob", blobToSave)
                                        }
                                    )
                                }
                            }
                            .toString()
                    } else {
                        "[]"
                    }

                chatViewModel.saveOutgoingMessage(
                    messageId = messageId,
                    text = text,
                    encryptedContent = encryptedContent,
                    encryptedKey =
                        if (encryptionContext.isGroup) {
                            chatViewModel.buildStoredGroupEncryptedKey(
                                encryptionContext.attachmentPassword,
                                command.senderPrivateKey
                            )
                        } else {
                            encryptedKey
                        },
                    timestamp = timestamp,
                    delivered = if (isSavedMessages) 1 else 0,
                    attachmentsJson = attachmentsJson,
                    accountPublicKey = command.senderPublicKey,
                    accountPrivateKey = command.senderPrivateKey,
                    opponentPublicKey = command.recipientPublicKey
                )

                chatViewModel.saveOutgoingDialog(
                    lastMessage = command.text,
                    timestamp = timestamp,
                    accountPublicKey = command.senderPublicKey,
                    accountPrivateKey = command.senderPrivateKey,
                    opponentPublicKey = command.recipientPublicKey
                )
            } catch (_: Exception) {
                withContext(Dispatchers.Main) {
                    chatViewModel.updateMessageStatusUi(messageId, MessageStatus.ERROR)
                }
                chatViewModel.updateMessageStatusDb(messageId, DeliveryStatus.ERROR.value)
                chatViewModel.saveOutgoingDialog(
                    lastMessage = command.text,
                    timestamp = timestamp,
                    accountPublicKey = command.senderPublicKey,
                    accountPrivateKey = command.senderPrivateKey,
                    opponentPublicKey = command.recipientPublicKey
                )
            } finally {
                triggerPendingTextSendIfReady("send_finished")
            }
        }
    }

    private fun shortSendKey(value: String?): String {
        val normalized = value?.trim().orEmpty()
        if (normalized.isEmpty()) return "<empty>"
        return if (normalized.length <= 12) normalized else "${normalized.take(12)}…"
    }

    private fun logSendBlocked(
        reason: String,
        textLength: Int,
        hasReply: Boolean,
        recipient: String?,
        sender: String?,
        hasPrivateKey: Boolean
    ) {
        chatViewModel.addProtocolLog(
            "⚠️ SEND_BLOCKED reason=$reason textLen=$textLength hasReply=$hasReply recipient=${shortSendKey(recipient)} sender=${shortSendKey(sender)} hasPriv=$hasPrivateKey isSending=${chatViewModel.isSendSlotBusy()}"
        )
    }

    private fun recoverRuntimeKeysIfMissing(): Boolean {
        if (chatViewModel.hasRuntimeKeysForSend()) return true

        val repositoryKeys = chatViewModel.resolveRepositoryRuntimeKeys()
        if (repositoryKeys != null) {
            chatViewModel.setUserKeys(repositoryKeys.first, repositoryKeys.second)
            chatViewModel.addProtocolLog(
                "🔄 SEND_RECOVERY restored_keys pk=${shortSendKey(repositoryKeys.first)}"
            )
        }

        return chatViewModel.hasRuntimeKeysForSend()
    }

    private fun schedulePendingTextSendRecovery(reason: String, hasPayload: Boolean) {
        if (!hasPayload) return
        pendingTextSendRequested = true
        pendingTextSendReason = reason

        if (pendingSendRecoveryJob?.isActive == true) return

        chatViewModel.addProtocolLog("⏳ SEND_RECOVERY queued reason=$reason")
        pendingSendRecoveryJob =
            chatViewModel.launchInViewModel {
                repeat(10) { attempt ->
                    delay(if (attempt < 4) 180L else 350L)
                    recoverRuntimeKeysIfMissing()
                    triggerPendingTextSendIfReady("timer_${attempt + 1}")
                    if (!pendingTextSendRequested) return@launchInViewModel
                }

                if (pendingTextSendRequested) {
                    chatViewModel.addProtocolLog(
                        "⚠️ SEND_RECOVERY timeout reason=$pendingTextSendReason"
                    )
                }
                clearPendingRecovery(cancelJob = false)
            }
    }

    private fun triggerPendingTextSendIfReady(trigger: String) {
        if (!pendingTextSendRequested) return

        val hasPayload =
            chatViewModel.inputText.value.trim().isNotEmpty() ||
                chatViewModel.replyMessages.value.isNotEmpty()
        if (!hasPayload) {
            clearPendingRecovery(cancelJob = true)
            return
        }

        val recipientReady = chatViewModel.currentRecipientForSend() != null
        val keysReady = chatViewModel.hasRuntimeKeysForSend()
        if (!recipientReady || !keysReady) return

        chatViewModel.addProtocolLog("🚀 SEND_RECOVERY flush trigger=$trigger")
        clearPendingRecovery(cancelJob = true)
        trySendMessage(allowPendingRecovery = false)
    }

    private fun clearPendingRecovery(cancelJob: Boolean) {
        if (cancelJob) {
            pendingSendRecoveryJob?.cancel()
        }
        pendingSendRecoveryJob = null
        pendingTextSendRequested = false
        pendingTextSendReason = ""
    }
}
