package com.rosetta.messenger.ui.chats

import android.app.Application
import android.util.Base64
import com.rosetta.messenger.crypto.CryptoManager
import com.rosetta.messenger.crypto.MessageCrypto
import com.rosetta.messenger.data.ForwardManager
import com.rosetta.messenger.domain.chats.usecase.ForwardPayloadMessage
import com.rosetta.messenger.domain.chats.usecase.SendForwardUseCase
import com.rosetta.messenger.network.AttachmentType
import com.rosetta.messenger.network.DeliveryStatus
import com.rosetta.messenger.network.MessageAttachment
import com.rosetta.messenger.network.PacketMessage
import com.rosetta.messenger.network.TransportManager
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.AttachmentFileManager
import java.io.File
import java.util.Date
import java.util.Locale
import java.util.UUID
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject

internal class ForwardCoordinator(
    private val chatViewModel: ChatViewModel,
    private val sendForwardUseCase: SendForwardUseCase
) {
    private val forwardUuidRegex =
        Regex(
            "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
        )

    private data class ForwardSourceMessage(
        val messageId: String,
        val senderPublicKey: String,
        val chachaKeyPlainHex: String,
        val attachments: List<MessageAttachment>
    )

    private data class ForwardRewriteResult(
        val rewrittenAttachments: Map<String, MessageAttachment>,
        val rewrittenMessageIds: Set<String>
    )

    fun sendForwardDirectly(
        recipientPublicKey: String,
        forwardMessages: List<ForwardManager.ForwardMessage>
    ) {
        val sender = chatViewModel.currentSenderPublicKeyForSend() ?: return
        val privateKey = chatViewModel.currentSenderPrivateKeyForSend() ?: return
        if (forwardMessages.isEmpty()) return

        val messageId = UUID.randomUUID().toString().replace("-", "").take(32)
        val timestamp = System.currentTimeMillis()
        val isCurrentDialogTarget = chatViewModel.isCurrentDialogTarget(recipientPublicKey)

        chatViewModel.launchOnIo {
            try {
                val context = chatViewModel.appContext()
                val isSavedMessages = (sender == recipientPublicKey)

                val encryptionContext =
                    chatViewModel.buildEncryptionContext(
                        plaintext = "",
                        recipient = recipientPublicKey,
                        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 replyAttachmentId = "reply_$timestamp"

                val forwardSources =
                    forwardMessages.map { message ->
                        ForwardSourceMessage(
                            messageId = message.messageId,
                            senderPublicKey = message.senderPublicKey,
                            chachaKeyPlainHex = message.chachaKeyPlain,
                            attachments = message.attachments
                        )
                    }
                val forwardRewriteResult =
                    prepareForwardAttachmentRewrites(
                        context = context,
                        sourceMessages = forwardSources,
                        encryptionContext = encryptionContext,
                        privateKey = privateKey,
                        isSavedMessages = isSavedMessages,
                        timestamp = timestamp
                    )
                val forwardedAttMap = forwardRewriteResult.rewrittenAttachments
                val rewrittenForwardMessageIds = forwardRewriteResult.rewrittenMessageIds
                val outgoingForwardPlainKeyHex =
                    encryptionContext.plainKeyAndNonce
                        ?.joinToString("") { "%02x".format(it) }
                        .orEmpty()
                val forwardPayloadMessages =
                    forwardMessages.map { message ->
                        ForwardPayloadMessage(
                            messageId = message.messageId,
                            senderPublicKey = message.senderPublicKey,
                            senderName = message.senderName,
                            text = message.text,
                            timestamp = message.timestamp,
                            chachaKeyPlain = message.chachaKeyPlain,
                            attachments = message.attachments
                        )
                    }

                fun buildForwardReplyJson(includeLocalUri: Boolean): JSONArray {
                    return sendForwardUseCase.buildForwardReplyJson(
                        messages = forwardPayloadMessages,
                        rewrittenAttachments = forwardedAttMap,
                        rewrittenMessageIds = rewrittenForwardMessageIds,
                        outgoingForwardPlainKeyHex = outgoingForwardPlainKeyHex,
                        includeLocalUri = includeLocalUri,
                        rewriteKey = ::forwardAttachmentRewriteKey
                    )
                }

                if (isCurrentDialogTarget) {
                    val optimisticForwardedMessages =
                        forwardMessages.map { message ->
                            val senderDisplayName =
                                message.senderName.ifEmpty {
                                    if (message.senderPublicKey == sender) "You" else "User"
                                }
                            ReplyData(
                                messageId = message.messageId,
                                senderName = senderDisplayName,
                                text = message.text,
                                isFromMe = message.senderPublicKey == sender,
                                isForwarded = true,
                                forwardedFromName = senderDisplayName,
                                attachments = message.attachments.filter { it.type != AttachmentType.MESSAGES },
                                senderPublicKey = message.senderPublicKey,
                                recipientPrivateKey = privateKey
                            )
                        }
                    withContext(Dispatchers.Main) {
                        chatViewModel.addOutgoingMessageOptimistic(
                            ChatMessage(
                                id = messageId,
                                text = "",
                                isOutgoing = true,
                                timestamp = Date(timestamp),
                                status = MessageStatus.SENDING,
                                forwardedMessages = optimisticForwardedMessages
                            )
                        )
                    }
                }

                val optimisticReplyBlobPlaintext = buildForwardReplyJson(includeLocalUri = true).toString()
                val optimisticReplyBlobForDatabase =
                    CryptoManager.encryptWithPassword(optimisticReplyBlobPlaintext, privateKey)

                val optimisticAttachmentsJson =
                    JSONArray()
                        .apply {
                            put(
                                JSONObject().apply {
                                    put("id", replyAttachmentId)
                                    put("type", AttachmentType.MESSAGES.value)
                                    put("preview", "")
                                    put("width", 0)
                                    put("height", 0)
                                    put("blob", optimisticReplyBlobForDatabase)
                                }
                            )
                        }
                        .toString()

                chatViewModel.saveOutgoingMessage(
                    messageId = messageId,
                    text = "",
                    encryptedContent = encryptedContent,
                    encryptedKey =
                        if (encryptionContext.isGroup) {
                            chatViewModel.buildStoredGroupEncryptedKey(
                                encryptionContext.attachmentPassword,
                                privateKey
                            )
                        } else {
                            encryptedKey
                        },
                    timestamp = timestamp,
                    delivered = if (isSavedMessages) 1 else 0,
                    attachmentsJson = optimisticAttachmentsJson,
                    accountPublicKey = sender,
                    accountPrivateKey = privateKey,
                    opponentPublicKey = recipientPublicKey
                )
                chatViewModel.refreshDialogFromMessagesForForward(sender, recipientPublicKey)

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

                val replyBlobPlaintext = buildForwardReplyJson(includeLocalUri = false).toString()
                val encryptedReplyBlob = encryptAttachmentPayload(replyBlobPlaintext, encryptionContext)
                val replyBlobForDatabase =
                    CryptoManager.encryptWithPassword(replyBlobPlaintext, privateKey)

                val finalMessageAttachments =
                    listOf(
                        sendForwardUseCase.buildForwardAttachment(
                            replyAttachmentId = replyAttachmentId,
                            encryptedReplyBlob = encryptedReplyBlob
                        )
                    )

                val packet =
                    PacketMessage().apply {
                        fromPublicKey = sender
                        toPublicKey = recipientPublicKey
                        content = encryptedContent
                        chachaKey = encryptedKey
                        this.aesChachaKey = aesChachaKey
                        this.timestamp = timestamp
                        this.privateKey = privateKeyHash
                        this.messageId = messageId
                        attachments = finalMessageAttachments
                    }
                sendForwardUseCase.dispatch(packet, isSavedMessages)

                val finalAttachmentsJson =
                    JSONArray()
                        .apply {
                            finalMessageAttachments.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(
                                            "blob",
                                            when (attachment.type) {
                                                AttachmentType.MESSAGES -> replyBlobForDatabase
                                                else -> ""
                                            }
                                        )
                                    }
                                )
                            }
                        }
                        .toString()

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

                if (isCurrentDialogTarget) {
                    withContext(Dispatchers.Main) {
                        if (isSavedMessages) {
                            chatViewModel.updateMessageStatusUi(messageId, MessageStatus.SENT)
                        }
                    }
                }
                chatViewModel.refreshDialogFromMessagesForForward(sender, recipientPublicKey)
            } catch (_: Exception) {
                if (isCurrentDialogTarget) {
                    withContext(Dispatchers.Main) {
                        chatViewModel.updateMessageStatusUi(messageId, MessageStatus.ERROR)
                    }
                }
                chatViewModel.updateMessageStatusDb(messageId, DeliveryStatus.ERROR.value)
            }
        }
    }

    fun encryptAttachmentPayloadForTextSend(
        payload: String,
        context: OutgoingEncryptionContext
    ): String {
        return encryptAttachmentPayload(payload, context)
    }

    fun forwardAttachmentRewriteKeyForTextSend(messageId: String, attachmentId: String): String {
        return forwardAttachmentRewriteKey(messageId, attachmentId)
    }

    suspend fun prepareForwardAttachmentRewritesForTextSend(
        sourceMessages: List<ChatViewModel.ReplyMessage>,
        encryptionContext: OutgoingEncryptionContext,
        privateKey: String,
        isSavedMessages: Boolean,
        timestamp: Long
    ): Pair<Map<String, MessageAttachment>, Set<String>> {
        val forwardSources =
            sourceMessages.map { message ->
                ForwardSourceMessage(
                    messageId = message.messageId,
                    senderPublicKey = message.publicKey,
                    chachaKeyPlainHex = message.chachaKeyPlainHex,
                    attachments = message.attachments
                )
            }

        val result =
            prepareForwardAttachmentRewrites(
                context = chatViewModel.appContext(),
                sourceMessages = forwardSources,
                encryptionContext = encryptionContext,
                privateKey = privateKey,
                isSavedMessages = isSavedMessages,
                timestamp = timestamp
            )

        return result.rewrittenAttachments to result.rewrittenMessageIds
    }

    private fun encryptAttachmentPayload(payload: String, context: OutgoingEncryptionContext): String {
        return CryptoManager.encryptWithPassword(payload, context.attachmentPassword)
    }

    private fun forwardAttachmentRewriteKey(messageId: String, attachmentId: String): String {
        return "$messageId::$attachmentId"
    }

    private fun shouldReuploadForwardAttachment(type: AttachmentType): Boolean {
        return type == AttachmentType.IMAGE ||
            type == AttachmentType.FILE ||
            type == AttachmentType.VOICE ||
            type == AttachmentType.VIDEO_CIRCLE
    }

    private fun decodeHexBytes(value: String): ByteArray? {
        val normalized = value.trim().lowercase(Locale.ROOT)
        if (normalized.isEmpty() || normalized.length % 2 != 0) return null
        return runCatching {
                ByteArray(normalized.length / 2) { index ->
                    normalized.substring(index * 2, index * 2 + 2).toInt(16).toByte()
                }
            }
            .getOrNull()
            ?.takeIf { it.isNotEmpty() }
    }

    private fun extractForwardFileName(preview: String): String {
        val normalized = preview.trim()
        if (normalized.isEmpty()) return ""
        val parts = normalized.split("::")
        return when {
            parts.size >= 3 && forwardUuidRegex.matches(parts[0]) -> {
                parts.drop(2).joinToString("::").trim()
            }

            parts.size >= 2 -> {
                parts.drop(1).joinToString("::").trim()
            }

            else -> normalized
        }
    }

    private fun decodeBase64PayloadForForward(value: String): ByteArray? {
        val normalized = value.trim()
        if (normalized.isEmpty()) return null
        val payload =
            when {
                normalized.contains("base64,", ignoreCase = true) -> {
                    normalized.substringAfter("base64,", "")
                }

                normalized.substringBefore(",").contains("base64", ignoreCase = true) -> {
                    normalized.substringAfter(",", "")
                }

                else -> normalized
            }
        if (payload.isEmpty()) return null
        return runCatching { Base64.decode(payload, Base64.DEFAULT) }.getOrNull()
    }

    private suspend fun resolveForwardAttachmentPayload(
        context: Application,
        sourceMessage: ForwardSourceMessage,
        attachment: MessageAttachment,
        privateKey: String
    ): String? {
        if (attachment.blob.isNotBlank()) {
            return attachment.blob
        }
        if (attachment.id.isBlank()) {
            return null
        }

        val normalizedPublicKey =
            sourceMessage.senderPublicKey.ifBlank {
                chatViewModel.currentSenderPublicKeyForSend()?.trim().orEmpty()
            }
        if (normalizedPublicKey.isNotBlank()) {
            val cachedPayload =
                AttachmentFileManager.readAttachment(
                    context = context,
                    attachmentId = attachment.id,
                    publicKey = normalizedPublicKey,
                    privateKey = privateKey
                )
            if (!cachedPayload.isNullOrBlank()) {
                return cachedPayload
            }
        }

        if (attachment.type == AttachmentType.FILE) {
            val fileName = extractForwardFileName(attachment.preview)
            if (fileName.isNotBlank()) {
                val localFile = File(context.filesDir, "rosetta_downloads/$fileName")
                if (localFile.exists() && localFile.length() > 0L) {
                    return runCatching {
                            Base64.encodeToString(localFile.readBytes(), Base64.NO_WRAP)
                        }
                        .getOrNull()
                }
            }
        }

        val downloadTag = attachment.transportTag.trim()
        val plainKey = decodeHexBytes(sourceMessage.chachaKeyPlainHex)
        if (downloadTag.isBlank() || plainKey == null) {
            return null
        }

        val encrypted =
            runCatching {
                    TransportManager.downloadFile(
                        attachment.id,
                        downloadTag,
                        attachment.transportServer.ifBlank { null }
                    )
                }
                .getOrNull()
                .orEmpty()
        if (encrypted.isBlank()) {
            return null
        }

        return MessageCrypto.decryptAttachmentBlobWithPlainKey(encrypted, plainKey)
            ?: MessageCrypto.decryptReplyBlob(encrypted, plainKey).takeIf { it.isNotEmpty() }
    }

    private suspend fun prepareForwardAttachmentRewrites(
        context: Application,
        sourceMessages: List<ForwardSourceMessage>,
        encryptionContext: OutgoingEncryptionContext,
        privateKey: String,
        isSavedMessages: Boolean,
        timestamp: Long
    ): ForwardRewriteResult {
        if (sourceMessages.isEmpty()) {
            return ForwardRewriteResult(emptyMap(), emptySet())
        }

        val rewritten = mutableMapOf<String, MessageAttachment>()
        val rewrittenMessageIds = mutableSetOf<String>()
        var forwardAttachmentIndex = 0

        for (sourceMessage in sourceMessages) {
            val candidates =
                sourceMessage.attachments.filter { shouldReuploadForwardAttachment(it.type) }
            if (candidates.isEmpty()) continue

            val stagedForMessage = mutableMapOf<String, MessageAttachment>()
            var allRewritten = true

            for (attachment in candidates) {
                val payload =
                    resolveForwardAttachmentPayload(
                        context = context,
                        sourceMessage = sourceMessage,
                        attachment = attachment,
                        privateKey = privateKey
                    )
                if (payload.isNullOrBlank()) {
                    allRewritten = false
                    break
                }

                val encryptedBlob = encryptAttachmentPayload(payload, encryptionContext)
                val newAttachmentId = "fwd_${timestamp}_${forwardAttachmentIndex++}"
                val uploadTag =
                    if (!isSavedMessages) {
                        runCatching { TransportManager.uploadFile(newAttachmentId, encryptedBlob) }
                            .getOrDefault("")
                    } else {
                        ""
                    }
                val transportServer =
                    if (uploadTag.isNotEmpty()) {
                        TransportManager.getTransportServer().orEmpty()
                    } else {
                        ""
                    }
                val normalizedPreview =
                    if (attachment.type == AttachmentType.IMAGE) {
                        attachment.preview.substringAfter("::", attachment.preview)
                    } else {
                        attachment.preview
                    }

                stagedForMessage[forwardAttachmentRewriteKey(sourceMessage.messageId, attachment.id)] =
                    attachment.copy(
                        id = newAttachmentId,
                        preview = normalizedPreview,
                        blob = "",
                        localUri = "",
                        transportTag = uploadTag,
                        transportServer = transportServer
                    )

                if (attachment.type == AttachmentType.IMAGE ||
                    attachment.type == AttachmentType.VOICE ||
                    attachment.type == AttachmentType.VIDEO_CIRCLE
                ) {
                    runCatching {
                        AttachmentFileManager.saveAttachment(
                            context = context,
                            blob = payload,
                            attachmentId = newAttachmentId,
                            publicKey =
                                sourceMessage.senderPublicKey.ifBlank {
                                    chatViewModel.currentSenderPublicKeyForSend()?.trim().orEmpty()
                                },
                            privateKey = privateKey
                        )
                    }
                }

                if (isSavedMessages && attachment.type == AttachmentType.FILE) {
                    val fileName = extractForwardFileName(attachment.preview)
                    val payloadBytes = decodeBase64PayloadForForward(payload)
                    if (fileName.isNotBlank() && payloadBytes != null) {
                        runCatching {
                            val downloadsDir =
                                File(context.filesDir, "rosetta_downloads").apply { mkdirs() }
                            File(downloadsDir, fileName).writeBytes(payloadBytes)
                        }
                    }
                }
            }

            if (allRewritten && stagedForMessage.size == candidates.size) {
                rewritten.putAll(stagedForMessage)
                rewrittenMessageIds.add(sourceMessage.messageId)
            }
        }

        return ForwardRewriteResult(
            rewrittenAttachments = rewritten,
            rewrittenMessageIds = rewrittenMessageIds
        )
    }
}
