package com.rosetta.messenger.ui.chats

import com.rosetta.messenger.crypto.CryptoManager
import com.rosetta.messenger.domain.chats.usecase.CreateVideoCircleAttachmentCommand
import com.rosetta.messenger.domain.chats.usecase.EncryptAndUploadAttachmentCommand
import com.rosetta.messenger.domain.chats.usecase.SendMediaMessageCommand
import com.rosetta.messenger.network.AttachmentType
import com.rosetta.messenger.network.MessageAttachment
import com.rosetta.messenger.ui.chats.models.MessageStatus
import com.rosetta.messenger.utils.AttachmentFileManager
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject

internal class AttachmentsCoordinator(
    private val chatViewModel: ChatViewModel
) {
    fun updateOptimisticImageMessage(
        messageId: String,
        base64: String,
        blurhash: String,
        width: Int,
        height: Int
    ) {
        val currentMessages = chatViewModel.currentMessagesForAttachments().toMutableList()
        val index = currentMessages.indexOfFirst { it.id == messageId }
        if (index == -1) return

        val message = currentMessages[index]
        val updatedAttachments =
            message.attachments.map { attachment ->
                if (attachment.type == AttachmentType.IMAGE) {
                    attachment.copy(
                        preview = blurhash,
                        blob = base64,
                        width = width,
                        height = height
                    )
                } else {
                    attachment
                }
            }

        currentMessages[index] = message.copy(attachments = updatedAttachments)
        chatViewModel.replaceMessagesForAttachments(
            messages = currentMessages,
            syncCache = false
        )
    }

    suspend fun sendImageMessageInternal(
        messageId: String,
        imageBase64: String,
        blurhash: String,
        caption: String,
        width: Int,
        height: Int,
        timestamp: Long,
        recipient: String,
        sender: String,
        privateKey: String
    ) {
        var packetSentToProtocol = false
        try {
            val context = chatViewModel.appContext()
            val pipelineStartedAt = System.currentTimeMillis()
            chatViewModel.logPhotoEvent(
                messageId,
                "internal send start: base64Len=${imageBase64.length}, blurhashLen=${blurhash.length}, captionLen=${caption.length}"
            )

            val encryptStartedAt = System.currentTimeMillis()
            val encryptionContext =
                chatViewModel.buildEncryptionContext(
                    plaintext = caption,
                    recipient = recipient,
                    privateKey = privateKey
                ) ?: throw IllegalStateException("Cannot resolve chat encryption context")
            val encryptedContent = encryptionContext.encryptedContent
            val encryptedKey = encryptionContext.encryptedKey
            val aesChachaKey = encryptionContext.aesChachaKey
            chatViewModel.logPhotoEvent(
                messageId,
                "text encrypted: contentLen=${encryptedContent.length}, keyLen=${encryptedKey.length}, elapsed=${System.currentTimeMillis() - encryptStartedAt}ms"
            )

            val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
            val blobEncryptStartedAt = System.currentTimeMillis()
            val attachmentId = "img_$timestamp"
            chatViewModel.logPhotoEvent(
                messageId,
                "attachment prepared: id=${chatViewModel.shortPhotoLogId(attachmentId, 12)}, size=${width}x$height"
            )

            val isSavedMessages = (sender == recipient)
            if (!isSavedMessages) {
                chatViewModel.logPhotoEvent(
                    messageId,
                    "upload start: attachment=${chatViewModel.shortPhotoLogId(attachmentId, 12)}"
                )
            }
            val uploadResult =
                chatViewModel.encryptAndUploadAttachment(
                    EncryptAndUploadAttachmentCommand(
                        payload = imageBase64,
                        attachmentPassword = encryptionContext.attachmentPassword,
                        attachmentId = attachmentId,
                        isSavedMessages = isSavedMessages
                    )
                )
            val uploadTag = uploadResult.transportTag
            val attachmentTransportServer = uploadResult.transportServer
            chatViewModel.logPhotoEvent(
                messageId,
                "blob encrypted: len=${uploadResult.encryptedBlob.length}, elapsed=${System.currentTimeMillis() - blobEncryptStartedAt}ms"
            )
            if (!isSavedMessages) {
                chatViewModel.logPhotoEvent(
                    messageId,
                    "upload done: tag=${chatViewModel.shortPhotoLogId(uploadTag, 12)}"
                )
            } else {
                chatViewModel.logPhotoEvent(messageId, "saved-messages mode: upload skipped")
            }

            val imageAttachment =
                MessageAttachment(
                    id = attachmentId,
                    blob = "",
                    type = AttachmentType.IMAGE,
                    preview = blurhash,
                    width = width,
                    height = height,
                    transportTag = uploadTag,
                    transportServer = attachmentTransportServer
                )

            chatViewModel.sendMediaMessage(
                SendMediaMessageCommand(
                    fromPublicKey = sender,
                    toPublicKey = recipient,
                    encryptedContent = encryptedContent,
                    encryptedKey = encryptedKey,
                    aesChachaKey = aesChachaKey,
                    privateKeyHash = privateKeyHash,
                    messageId = messageId,
                    timestamp = timestamp,
                    mediaAttachments = listOf(imageAttachment),
                    isSavedMessages = isSavedMessages
                )
            )
            if (!isSavedMessages) {
                packetSentToProtocol = true
                chatViewModel.logPhotoEvent(messageId, "packet sent to protocol")
            } else {
                chatViewModel.logPhotoEvent(messageId, "saved-messages mode: packet send skipped")
            }

            val savedLocally =
                AttachmentFileManager.saveAttachment(
                    context = context,
                    blob = imageBase64,
                    attachmentId = attachmentId,
                    publicKey = sender,
                    privateKey = privateKey
                )
            chatViewModel.logPhotoEvent(messageId, "local file cache saved=$savedLocally")

            val attachmentsJson =
                JSONArray()
                    .apply {
                        put(
                            JSONObject().apply {
                                put("id", attachmentId)
                                put("type", AttachmentType.IMAGE.value)
                                put("preview", blurhash)
                                put("blob", "")
                                put("width", width)
                                put("height", height)
                                put("transportTag", uploadTag)
                                put("transportServer", attachmentTransportServer)
                            }
                        )
                    }
                    .toString()

            val deliveryStatus = if (isSavedMessages) 1 else 0
            chatViewModel.updateMessageStatusAndAttachmentsDb(
                messageId = messageId,
                delivered = deliveryStatus,
                attachmentsJson = attachmentsJson
            )
            chatViewModel.logPhotoEvent(messageId, "db status+attachments updated")

            withContext(Dispatchers.Main) {
                chatViewModel.updateMessageStatusUi(messageId, MessageStatus.SENT)
                clearAttachmentLocalUri(messageId)
            }
            chatViewModel.logPhotoEvent(messageId, "ui status switched to SENT")

            chatViewModel.saveOutgoingDialog(
                lastMessage = if (caption.isNotEmpty()) caption else "photo",
                timestamp = timestamp,
                accountPublicKey = sender,
                accountPrivateKey = privateKey,
                opponentPublicKey = recipient
            )
            chatViewModel.logPhotoEvent(
                messageId,
                "dialog updated; totalElapsed=${System.currentTimeMillis() - pipelineStartedAt}ms"
            )
        } catch (e: CancellationException) {
            chatViewModel.logPhotoEvent(messageId, "internal-send cancelled")
            throw e
        } catch (e: Exception) {
            chatViewModel.logPhotoErrorEvent(messageId, "internal-send", e)
            if (packetSentToProtocol) {
                withContext(Dispatchers.Main) {
                    chatViewModel.updateMessageStatusUi(messageId, MessageStatus.SENT)
                }
                chatViewModel.logPhotoEvent(messageId, "post-send non-fatal error: status kept as SENT")
            } else {
                withContext(Dispatchers.Main) {
                    chatViewModel.updateMessageStatusUi(messageId, MessageStatus.ERROR)
                }
            }
        }
    }

    suspend fun sendVideoCircleMessageInternal(
        messageId: String,
        attachmentId: String,
        timestamp: Long,
        videoHex: String,
        preview: String,
        width: Int,
        height: Int,
        recipient: String,
        sender: String,
        privateKey: String
    ) {
        var packetSentToProtocol = false
        try {
            val application = chatViewModel.appContext()

            val encryptionContext =
                chatViewModel.buildEncryptionContext(
                    plaintext = "",
                    recipient = recipient,
                    privateKey = privateKey
                ) ?: throw IllegalStateException("Cannot resolve chat encryption context")
            val encryptedContent = encryptionContext.encryptedContent
            val encryptedKey = encryptionContext.encryptedKey
            val aesChachaKey = encryptionContext.aesChachaKey
            val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)

            val isSavedMessages = (sender == recipient)
            val uploadResult =
                chatViewModel.encryptAndUploadAttachment(
                    EncryptAndUploadAttachmentCommand(
                        payload = videoHex,
                        attachmentPassword = encryptionContext.attachmentPassword,
                        attachmentId = attachmentId,
                        isSavedMessages = isSavedMessages
                    )
                )
            val uploadTag = uploadResult.transportTag
            val attachmentTransportServer = uploadResult.transportServer

            val videoAttachment =
                chatViewModel.createVideoCircleAttachment(
                    CreateVideoCircleAttachmentCommand(
                        attachmentId = attachmentId,
                        preview = preview,
                        width = width,
                        height = height,
                        blob = "",
                        transportTag = uploadTag,
                        transportServer = attachmentTransportServer
                    )
                )

            chatViewModel.sendMediaMessage(
                SendMediaMessageCommand(
                    fromPublicKey = sender,
                    toPublicKey = recipient,
                    encryptedContent = encryptedContent,
                    encryptedKey = encryptedKey,
                    aesChachaKey = aesChachaKey,
                    privateKeyHash = privateKeyHash,
                    messageId = messageId,
                    timestamp = timestamp,
                    mediaAttachments = listOf(videoAttachment),
                    isSavedMessages = isSavedMessages
                )
            )
            if (!isSavedMessages) {
                packetSentToProtocol = true
            }

            runCatching {
                AttachmentFileManager.saveAttachment(
                    context = application,
                    blob = videoHex,
                    attachmentId = attachmentId,
                    publicKey = sender,
                    privateKey = privateKey
                )
            }

            val attachmentsJson =
                JSONArray()
                    .apply {
                        put(
                            JSONObject().apply {
                                put("id", attachmentId)
                                put("type", AttachmentType.VIDEO_CIRCLE.value)
                                put("preview", preview)
                                put("blob", "")
                                put("width", width)
                                put("height", height)
                                put("transportTag", uploadTag)
                                put("transportServer", attachmentTransportServer)
                            }
                        )
                    }
                    .toString()

            chatViewModel.updateMessageStatusAndAttachmentsDb(
                messageId = messageId,
                delivered = if (isSavedMessages) 1 else 0,
                attachmentsJson = attachmentsJson
            )

            withContext(Dispatchers.Main) {
                chatViewModel.updateMessageStatusUi(messageId, MessageStatus.SENT)
                clearAttachmentLocalUri(messageId)
            }
            chatViewModel.saveOutgoingDialog(
                lastMessage = "Video message",
                timestamp = timestamp,
                accountPublicKey = sender,
                accountPrivateKey = privateKey,
                opponentPublicKey = recipient
            )
        } catch (_: Exception) {
            if (packetSentToProtocol) {
                withContext(Dispatchers.Main) {
                    chatViewModel.updateMessageStatusUi(messageId, MessageStatus.SENT)
                }
            } else {
                withContext(Dispatchers.Main) {
                    chatViewModel.updateMessageStatusUi(messageId, MessageStatus.ERROR)
                }
            }
        }
    }

    private fun clearAttachmentLocalUri(messageId: String) {
        val updatedMessages =
            chatViewModel.currentMessagesForAttachments().map { message ->
                if (message.id == messageId) {
                    val updatedAttachments =
                        message.attachments.map { attachment ->
                            attachment.copy(localUri = "")
                        }
                    message.copy(attachments = updatedAttachments)
                } else {
                    message
                }
            }

        chatViewModel.replaceMessagesForAttachments(
            messages = updatedMessages,
            syncCache = true
        )
    }
}
