package com.rosetta.messenger.ui.chats

import android.app.Application
import androidx.compose.runtime.Immutable
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.rosetta.messenger.crypto.CryptoManager
import com.rosetta.messenger.data.DraftManager
import com.rosetta.messenger.data.GroupRepository
import com.rosetta.messenger.data.MessageRepository
import com.rosetta.messenger.database.BlacklistEntity
import com.rosetta.messenger.database.RosettaDatabase
import com.rosetta.messenger.network.PacketOnlineSubscribe
import com.rosetta.messenger.network.PacketSearch
import com.rosetta.messenger.network.ProtocolManager
import com.rosetta.messenger.network.SearchUser
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/** UI модель диалога с расшифрованным lastMessage */
@Immutable
data class DialogUiModel(
        val id: Long,
        val account: String,
        val opponentKey: String,
        val opponentTitle: String,
        val opponentUsername: String,
        val lastMessage: String, // 🔓 Расшифрованный текст
        val lastMessageTimestamp: Long,
        val unreadCount: Int,
        val isOnline: Int,
        val lastSeen: Long,
        val verified: Int,
        val isSavedMessages: Boolean = false, // 📁 Флаг для Saved Messages (account == opponentKey)
        val lastMessageFromMe: Int = 0, // Последнее сообщение от меня (0/1)
        val lastMessageDelivered: Int = 0, // Статус доставки (0=WAITING, 1=DELIVERED, 2=ERROR)
        val lastMessageRead: Int = 0, // Прочитано (0/1)
        val lastMessageAttachmentType: String? =
                null, // 📎 Тип attachment: "Photo", "File", или null
        val lastMessageSenderPrefix: String? = null, // 👥 Для групп: "You" или имя отправителя
        val lastMessageSenderKey: String? = null, // 👥 Для групп: public key отправителя
        val draftText: String? = null // 📝 Черновик сообщения (как в Telegram)
)

/**
 * 🔥 Комбинированное состояние чатов для атомарного обновления UI Это предотвращает "дергание"
 * когда dialogs и requests обновляются независимо
 */
@Immutable
data class ChatsUiState(
        val dialogs: List<DialogUiModel> = emptyList(),
        val requests: List<DialogUiModel> = emptyList(),
        val requestsCount: Int = 0
) {
    val isEmpty: Boolean
        get() = dialogs.isEmpty() && requestsCount == 0
    val hasContent: Boolean
        get() = dialogs.isNotEmpty() || requestsCount > 0
}

/** ViewModel для списка чатов Загружает диалоги из базы данных и расшифровывает lastMessage */
class ChatsListViewModel(application: Application) : AndroidViewModel(application) {

    private val database = RosettaDatabase.getDatabase(application)
    private val dialogDao = database.dialogDao()
    private val groupRepository = GroupRepository.getInstance(application)

    private var currentAccount: String = ""
    private var currentPrivateKey: String? = null

    // 🔥 Tracking для уже запрошенных user info - предотвращает бесконечные запросы
    private val requestedUserInfoKeys = mutableSetOf<String>()

    // 🟢 Tracking для уже подписанных онлайн-статусов - предотвращает бесконечный цикл
    private val subscribedOnlineKeys = mutableSetOf<String>()

    // Job для отмены подписок при смене аккаунта
    private var accountSubscriptionsJob: Job? = null

    // Список диалогов с расшифрованными сообщениями
    private val _dialogs = MutableStateFlow<List<DialogUiModel>>(emptyList())
    val dialogs: StateFlow<List<DialogUiModel>> = _dialogs.asStateFlow()

    // Список requests (запросы от новых пользователей)
    private val _requests = MutableStateFlow<List<DialogUiModel>>(emptyList())
    val requests: StateFlow<List<DialogUiModel>> = _requests.asStateFlow()

    // Количество requests
    private val _requestsCount = MutableStateFlow(0)
    val requestsCount: StateFlow<Int> = _requestsCount.asStateFlow()

    // Заблокированные пользователи (реактивный Set из Room Flow)
    private val _blockedUsers = MutableStateFlow<Set<String>>(emptySet())
    val blockedUsers: StateFlow<Set<String>> = _blockedUsers.asStateFlow()

    // 🔥 НОВОЕ: Комбинированное состояние - обновляется атомарно!
    // 🎯 ИСПРАВЛЕНИЕ: debounce предотвращает мерцание при быстрых обновлениях
    // 📝 Комбинируем с drafts для показа черновиков в списке чатов
    val chatsState: StateFlow<ChatsUiState> =
            combine(_dialogs, _requests, _requestsCount, DraftManager.drafts) { dialogs, requests, count, drafts ->
                        // 📝 Обогащаем диалоги черновиками
                        val dialogsWithDrafts = if (drafts.isEmpty()) dialogs else {
                            dialogs.map { dialog ->
                                val draft = drafts[dialog.opponentKey]
                                if (draft != null) dialog.copy(draftText = draft) else dialog
                            }
                        }
                        ChatsUiState(dialogsWithDrafts, requests, count)
                    }
                    .distinctUntilChanged() // 🔥 Игнорируем дублирующиеся состояния
                    .stateIn(
                            viewModelScope,
                            SharingStarted
                                    .Eagerly, // 🔥 КРИТИЧНО: Eagerly вместо WhileSubscribed - сразу
                            // начинаем следить
                            ChatsUiState()
                    )

    // Загрузка (🔥 true по умолчанию — skeleton на первом кадре, чтобы не мигало empty→skeleton→empty)
    private val _isLoading = MutableStateFlow(true)
    val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()

    private val TAG = "ChatsListVM"
    private val groupInviteRegex = Regex("^#group:[A-Za-z0-9+/=:]+$")
    private val groupJoinedMarker = "\$a=Group joined"
    private val groupCreatedMarker = "\$a=Group created"

    private fun isGroupKey(value: String): Boolean {
        val normalized = value.trim().lowercase()
        return normalized.startsWith("#group:") || normalized.startsWith("group:")
    }

    private data class GroupLastSenderInfo(
            val senderPrefix: String,
            val senderKey: String
    )

    private suspend fun resolveGroupLastSenderInfo(
            dialog: com.rosetta.messenger.database.DialogEntity
    ): GroupLastSenderInfo? {
        if (!isGroupKey(dialog.opponentKey) || dialog.lastMessageTimestamp <= 0L) return null

        val senderKey =
                if (dialog.lastMessageFromMe == 1) {
                    currentAccount
                } else {
                    val lastMessage =
                            try {
                                dialogDao.getLastMessageByDialogKey(
                                        account = dialog.account,
                                        dialogKey = dialog.opponentKey.trim()
                                )
                            } catch (_: Exception) {
                                null
                            }
                    lastMessage?.fromPublicKey.orEmpty()
                }

        if (senderKey.isBlank()) return null
        if (senderKey == currentAccount) {
            return GroupLastSenderInfo(senderPrefix = "You", senderKey = senderKey)
        }

        val senderName = resolveKnownDisplayName(senderKey)
        if (senderName == null) {
            loadUserInfoForDialog(senderKey)
        }
        return GroupLastSenderInfo(
                senderPrefix = senderName ?: senderKey.take(7),
                senderKey = senderKey
        )
    }

    private suspend fun resolveKnownDisplayName(publicKey: String): String? {
        if (publicKey.isBlank()) return null
        if (publicKey == currentAccount) return "You"

        val dialogName =
                try {
                    val userDialog = dialogDao.getDialog(currentAccount, publicKey)
                    userDialog?.let {
                        extractDisplayName(
                                title = it.opponentTitle,
                                username = it.opponentUsername,
                                publicKey = publicKey
                        )
                    }
                } catch (_: Exception) {
                    null
                }
        if (!dialogName.isNullOrBlank()) return dialogName

        val cached = ProtocolManager.getCachedUserName(publicKey).orEmpty().trim()
        if (cached.isNotBlank() && cached != publicKey) {
            return cached
        }

        return null
    }

    private fun extractDisplayName(title: String, username: String, publicKey: String): String? {
        val normalizedTitle = title.trim()
        if (normalizedTitle.isNotEmpty() &&
                        normalizedTitle != publicKey &&
                        normalizedTitle != publicKey.take(7) &&
                        normalizedTitle != publicKey.take(8)
        ) {
            return normalizedTitle
        }

        val normalizedUsername = username.trim()
        if (normalizedUsername.isNotEmpty() &&
                        normalizedUsername != publicKey &&
                        normalizedUsername != publicKey.take(7) &&
                        normalizedUsername != publicKey.take(8)
        ) {
            return normalizedUsername
        }

        return null
    }

    /** Установить текущий аккаунт и загрузить диалоги */
    fun setAccount(publicKey: String, privateKey: String) {
        val setAccountStart = System.currentTimeMillis()
        if (currentAccount == publicKey) {
            // 🔥 Сбрасываем skeleton если он ещё показан (при повторном заходе)
            if (_isLoading.value) _isLoading.value = false
            return
        }

        // 🔥 Показываем skeleton пока данные грузятся
        _isLoading.value = true

        // 🔥 Очищаем кэш запрошенных user info при смене аккаунта
        requestedUserInfoKeys.clear()
        subscribedOnlineKeys.clear()

        // 🔥 Очищаем глобальный кэш сообщений ChatViewModel при смене аккаунта
        // чтобы избежать показа сообщений с неправильным isOutgoing
        ChatViewModel.clearAllDialogCache()

        // Отменяем старые подписки от предыдущего аккаунта
        accountSubscriptionsJob?.cancel()

        currentAccount = publicKey
        currentPrivateKey = privateKey

        // � Устанавливаем аккаунт для DraftManager (загрузит черновики из SharedPreferences)
        DraftManager.setAccount(publicKey)

        // �🔥 Очищаем устаревшие данные от предыдущего аккаунта
        _dialogs.value = emptyList()
        _requests.value = emptyList()
        _requestsCount.value = 0

        // 🚀 Pre-warm PBKDF2 ключ — чтобы к моменту дешифровки кэш был горячий
        viewModelScope.launch(Dispatchers.Default) { CryptoManager.getPbkdf2Key(privateKey) }

        // Запускаем все подписки в одном родительском Job для отмены при смене аккаунта
        accountSubscriptionsJob = viewModelScope.launch {

        // 🟢 Сбрасываем устаревшие online-флаги из прошлого сеанса.
        // Актуальные статусы придут сразу после PacketOnlineSubscribe.
        withContext(Dispatchers.IO) {
            runCatching { dialogDao.clearOnlineStatuses(publicKey) }
        }

        // Подписываемся на обычные диалоги
        @OptIn(FlowPreview::class)
        launch {
            dialogDao
                    .getDialogsFlow(publicKey)
                    .flowOn(Dispatchers.IO) // 🚀 Flow работает на IO
                    .debounce(100) // 🚀 Батчим быстрые обновления (N сообщений → 1 обработка)
                    .map { dialogsList ->
                        val mapStart = System.currentTimeMillis()
                        // � ОПТИМИЗАЦИЯ: Параллельная расшифровка всех сообщений
                        withContext(Dispatchers.Default) {
                            dialogsList
                                    .map { dialog ->
                                        async {
                                            // 🔥 Загружаем информацию о пользователе если её нет
                                            // 📁 НЕ загружаем для Saved Messages
                                            val isSavedMessages =
                                                    (dialog.account == dialog.opponentKey)
                                            if (!isSavedMessages &&
                                                            (dialog.opponentTitle.isEmpty() ||
                                                                    dialog.opponentTitle ==
                                                                            dialog.opponentKey ||
                                                                    dialog.opponentTitle ==
                                                                            dialog.opponentKey.take(
                                                                                    7
                                                                            ) ||
                                                                    dialog.opponentTitle ==
                                                                            dialog.opponentKey.take(
                                                                                    8
                                                                            ))
                                            ) {
                                                loadUserInfoForDialog(dialog.opponentKey)
                                            }

                                            // Безопасная дешифровка превью: никогда не показываем raw ciphertext.
                                            val decryptedLastMessage =
                                                    decryptDialogPreview(
                                                            encryptedLastMessage =
                                                                    dialog.lastMessage,
                                                            privateKey = privateKey
                                                    )

                                            // � ОПТИМИЗАЦИЯ: Используем денормализованные поля из
                                            // DialogEntity
                                            // Статус и attachments уже записаны в dialogs через
                                            // updateDialogFromMessages()
                                            // Это устраняет N+1 проблему (ранее: 2 запроса на
                                            // каждый диалог)

                                            // 📎 Определяем тип attachment из кэшированного поля в
                                            // DialogEntity
                                            val attachmentType =
                                                    resolveAttachmentType(
                                                            attachmentsJson =
                                                                    dialog.lastMessageAttachments,
                                                            decryptedLastMessage =
                                                                    decryptedLastMessage
                                                    )
                                            val groupLastSenderInfo =
                                                    resolveGroupLastSenderInfo(dialog)

                                            DialogUiModel(
                                                    id = dialog.id,
                                                    account = dialog.account,
                                                    opponentKey = dialog.opponentKey,
                                                    opponentTitle = dialog.opponentTitle,
                                                    opponentUsername = dialog.opponentUsername,
                                                    lastMessage = decryptedLastMessage,
                                                    lastMessageTimestamp =
                                                            dialog.lastMessageTimestamp,
                                                    unreadCount = dialog.unreadCount,
                                                    isOnline = dialog.isOnline,
                                                    lastSeen = dialog.lastSeen,
                                                    verified = dialog.verified,
                                                    isSavedMessages =
                                                            isSavedMessages, // 📁 Saved Messages
                                                    lastMessageFromMe =
                                                            dialog.lastMessageFromMe, // 🚀 Из
                                                    // DialogEntity (денормализовано)
                                                    lastMessageDelivered =
                                                            dialog.lastMessageDelivered, // 🚀 Из
                                                    // DialogEntity (денормализовано)
                                                    lastMessageRead =
                                                            dialog.lastMessageRead, // 🚀 Из
                                                    // DialogEntity
                                                    // (денормализовано)
                                                    lastMessageAttachmentType =
                                                            attachmentType, // 📎 Тип attachment
                                                    lastMessageSenderPrefix =
                                                            groupLastSenderInfo?.senderPrefix,
                                                    lastMessageSenderKey =
                                                            groupLastSenderInfo?.senderKey
                                            )
                                        }
                                    }
                                    .awaitAll()
                        }
                                .also {
                                    val mapTime = System.currentTimeMillis() - mapStart
                                }
                    }
                    .distinctUntilChanged() // 🔥 ИСПРАВЛЕНИЕ: Игнорируем дублирующиеся списки
                    .collect { decryptedDialogs ->
                        // Deduplicate by opponentKey to prevent LazyColumn crash
                        // (Key "X" was already used)
                        _dialogs.value = decryptedDialogs.distinctBy { it.opponentKey }
                        // 🚀 Убираем skeleton после первой загрузки
                        if (_isLoading.value) _isLoading.value = false

                        // 🟢 Подписываемся на онлайн-статусы всех собеседников
                        // 📁 Исключаем Saved Messages - не нужно подписываться на свой собственный
                        // статус
                        val opponentsToSubscribe =
                                decryptedDialogs.filter { !it.isSavedMessages }.map {
                                    it.opponentKey
                                }
                        subscribeToOnlineStatuses(opponentsToSubscribe, privateKey)
                    }
        }

        // 📬 Подписываемся на requests (запросы от новых пользователей)
        @OptIn(FlowPreview::class)
        launch {
            dialogDao
                    .getRequestsFlow(publicKey)
                    .flowOn(Dispatchers.IO)
                    .debounce(100) // 🚀 Батчим быстрые обновления
                    .map { requestsList ->
                        // 🚀 ОПТИМИЗАЦИЯ: Параллельная расшифровка
                        withContext(Dispatchers.Default) {
                            requestsList
                                    .map { dialog ->
                                        async {
                                            // 🔥 Загружаем информацию о пользователе если её нет
                                            // 📁 НЕ загружаем для Saved Messages
                                            val isSavedMessages =
                                                    (dialog.account == dialog.opponentKey)
                                            if (!isSavedMessages &&
                                                            (dialog.opponentTitle.isEmpty() ||
                                                                    dialog.opponentTitle ==
                                                                            dialog.opponentKey)
                                            ) {
                                                loadUserInfoForRequest(dialog.opponentKey)
                                            }

                                            // Безопасная дешифровка превью: никогда не показываем raw ciphertext.
                                            val decryptedLastMessage =
                                                    decryptDialogPreview(
                                                            encryptedLastMessage =
                                                                    dialog.lastMessage,
                                                            privateKey = privateKey
                                                    )

                                            // 📎 Определяем тип attachment из кэшированного поля в
                                            // DialogEntity
                                            val attachmentType =
                                                    resolveAttachmentType(
                                                            attachmentsJson =
                                                                    dialog.lastMessageAttachments,
                                                            decryptedLastMessage =
                                                                    decryptedLastMessage
                                                    )
                                            val groupLastSenderInfo =
                                                    resolveGroupLastSenderInfo(dialog)

                                            DialogUiModel(
                                                    id = dialog.id,
                                                    account = dialog.account,
                                                    opponentKey = dialog.opponentKey,
                                                    opponentTitle =
                                                            dialog.opponentTitle, // 🔥 Показываем
                                                    // имя как в
                                                    // обычных чатах
                                                    opponentUsername = dialog.opponentUsername,
                                                    lastMessage = decryptedLastMessage,
                                                    lastMessageTimestamp =
                                                            dialog.lastMessageTimestamp,
                                                    unreadCount = dialog.unreadCount,
                                                    isOnline = dialog.isOnline,
                                                    lastSeen = dialog.lastSeen,
                                                    verified = dialog.verified,
                                                    isSavedMessages =
                                                            (dialog.account ==
                                                                    dialog.opponentKey), // 📁 Saved
                                                    // Messages
                                                    lastMessageFromMe = dialog.lastMessageFromMe,
                                                    lastMessageDelivered =
                                                            dialog.lastMessageDelivered,
                                                    lastMessageRead = dialog.lastMessageRead,
                                                    lastMessageAttachmentType =
                                                            attachmentType, // 📎 Тип attachment
                                                    lastMessageSenderPrefix =
                                                            groupLastSenderInfo?.senderPrefix,
                                                    lastMessageSenderKey =
                                                            groupLastSenderInfo?.senderKey
                                            )
                                        }
                                    }
                                    .awaitAll()
                        }
                    }
                    .distinctUntilChanged() // 🔥 ИСПРАВЛЕНИЕ: Игнорируем дублирующиеся списки
                    .collect { decryptedRequests ->
                        _requests.value = decryptedRequests.distinctBy { it.opponentKey }
                    }
        }

        // 📊 Подписываемся на количество requests
        launch {
            dialogDao
                    .getRequestsCountFlow(publicKey)
                    .flowOn(Dispatchers.IO)
                    .distinctUntilChanged() // 🔥 ИСПРАВЛЕНИЕ: Игнорируем дублирующиеся значения
                    .collect { count -> _requestsCount.value = count }
        }

        // 🔥 Подписываемся на блоклист — Room автоматически переэмитит при
        // blockUser()/unblockUser()
        launch {
            database.blacklistDao()
                    .getBlockedUsers(publicKey)
                    .flowOn(Dispatchers.IO)
                    .map { entities -> entities.map { it.publicKey }.toSet() }
                    .distinctUntilChanged()
                    .collect { blockedSet -> _blockedUsers.value = blockedSet }
        }

        // Desktop parity: when sync finishes (syncInProgress transitions true → false),
        // clear the one-shot requestedUserInfoKeys guard so the dialog-list .map{} block
        // can re-trigger loadUserInfoForDialog() on the next Room emission for any
        // dialogs that still have empty titles.
        launch {
            var wasSyncing = false
            ProtocolManager.syncInProgress.collect { syncing ->
                if (wasSyncing && !syncing) {
                    requestedUserInfoKeys.clear()
                }
                wasSyncing = syncing
            }
        }

        } // end accountSubscriptionsJob
    }

    /**
     * 🟢 Подписаться на онлайн-статусы всех собеседников 🔥 Фильтруем уже подписанные ключи чтобы
     * избежать бесконечного цикла
     */
    private fun subscribeToOnlineStatuses(opponentKeys: List<String>, privateKey: String) {
        if (opponentKeys.isEmpty()) return

        // 🔥 КРИТИЧНО: Фильтруем уже подписанные ключи!
        val newKeys =
                opponentKeys.filter { key ->
                    !subscribedOnlineKeys.contains(key) && !isGroupKey(key)
                }
        if (newKeys.isEmpty()) return // Все уже подписаны

        // Добавляем в Set ДО отправки пакета чтобы избежать race condition
        subscribedOnlineKeys.addAll(newKeys)

        viewModelScope.launch(Dispatchers.IO) {
            try {
                val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)

                val packet =
                        PacketOnlineSubscribe().apply {
                            this.privateKey = privateKeyHash
                            newKeys.forEach { key -> addPublicKey(key) }
                        }

                ProtocolManager.send(packet)
            } catch (e: Exception) {}
        }
    }

    private fun decryptDialogPreview(encryptedLastMessage: String, privateKey: String): String {
        if (encryptedLastMessage.isEmpty()) return ""

        if (privateKey.isEmpty()) {
            val plainCandidate =
                    if (isLikelyEncryptedPayload(encryptedLastMessage)) "" else encryptedLastMessage
            return formatPreviewText(plainCandidate)
        }

        val decrypted =
                try {
                    CryptoManager.decryptWithPassword(encryptedLastMessage, privateKey)
                } catch (e: Exception) {
                    null
                }

        val resolved = when {
            decrypted != null -> decrypted
            isLikelyEncryptedPayload(encryptedLastMessage) -> ""
            else -> encryptedLastMessage
        }
        return formatPreviewText(resolved)
    }

    private fun formatPreviewText(value: String): String {
        if (value.isBlank()) return value
        val normalized = value.trim()
        return when {
            groupInviteRegex.matches(normalized) -> "Group Invite"
            normalized == groupJoinedMarker -> "You joined the group"
            normalized == groupCreatedMarker -> "Group created"
            else -> value
        }
    }

    private fun resolveAttachmentType(
            attachmentsJson: String,
            decryptedLastMessage: String
    ): String? {
        if (attachmentsJson.isEmpty() || attachmentsJson == "[]") return null

        return try {
            val attachments = org.json.JSONArray(attachmentsJson)
            if (attachments.length() == 0) return null

            val firstAttachment = attachments.getJSONObject(0)
            when (firstAttachment.optInt("type", -1)) {
                0 -> "Photo" // AttachmentType.IMAGE = 0
                1 -> {
                    // AttachmentType.MESSAGES = 1 (Reply/Forward).
                    // Если текст пустой — показываем "Forwarded" как в desktop.
                    if (decryptedLastMessage.isNotEmpty()) null else "Forwarded"
                }
                2 -> "File" // AttachmentType.FILE = 2
                3 -> "Avatar" // AttachmentType.AVATAR = 3
                4 -> "Call" // AttachmentType.CALL = 4
                else -> null
            }
        } catch (e: Exception) {
            // Fallback: если JSON поврежден, но видно MESSAGES attachment и текста нет — это forward.
            val hasMessagesType =
                    attachmentsJson.contains("\"type\":1") ||
                            attachmentsJson.contains("\"type\": 1")
            if (decryptedLastMessage.isEmpty() && hasMessagesType) {
                "Forwarded"
            } else {
                null
            }
        }
    }

    private fun isLikelyEncryptedPayload(value: String): Boolean {
        if (value.startsWith("CHNK:")) return true

        val parts = value.split(":", limit = 2)
        if (parts.size != 2) return false

        fun isBase64Like(part: String): Boolean {
            if (part.length < 12) return false
            return part.all { ch ->
                ch.isLetterOrDigit() ||
                        ch == '+' || ch == '/' || ch == '=' ||
                        ch == '-' || ch == '_'
            }
        }

        return isBase64Like(parts[0]) && isBase64Like(parts[1])
    }

    /**
     * Создать или обновить диалог после отправки/получения сообщения 🔥 Используем
     * updateDialogFromMessages для пересчета счетчиков из messages 📁 SAVED MESSAGES: Использует
     * специальный метод для saved messages
     */
    suspend fun upsertDialog(
            opponentKey: String,
            opponentTitle: String,
            opponentUsername: String = "",
            lastMessage: String,
            timestamp: Long,
            verified: Int = 0,
            isOnline: Int = 0
    ) {
        if (currentAccount.isEmpty()) return

        try {
            // 🔥 КРИТИЧНО: Используем updateDialogFromMessages который пересчитывает счетчики
            // напрямую из таблицы messages, как в Архиве!
            // 📁 Используем специальный метод для saved messages
            if (opponentKey == currentAccount) {
                dialogDao.updateSavedMessagesDialogFromMessages(currentAccount)
            } else {
                dialogDao.updateDialogFromMessages(currentAccount, opponentKey)
            }

            // Обновляем информацию о собеседнике если есть
            if (opponentTitle.isNotEmpty()) {
                dialogDao.updateOpponentInfo(
                        currentAccount,
                        opponentKey,
                        opponentTitle,
                        opponentUsername,
                        verified
                )
            }
        } catch (e: Exception) {}
    }

    /** Конвертировать DialogUiModel в SearchUser для навигации */
    fun dialogToSearchUser(dialog: DialogUiModel): SearchUser {
        return SearchUser(
                title = dialog.opponentTitle,
                username = dialog.opponentUsername,
                publicKey = dialog.opponentKey,
                verified = dialog.verified,
                online = dialog.isOnline
        )
    }

    /**
     * Удалить диалог и все сообщения с собеседником 🔥 ПОЛНОСТЬЮ очищает все данные - диалог,
     * сообщения, кэш
     */
    suspend fun deleteDialog(opponentKey: String) {
        if (currentAccount.isEmpty()) return

        try {
            // 🚀 Сразу обновляем UI - удаляем диалог из локального списка
            _dialogs.value = _dialogs.value.filter { it.opponentKey != opponentKey }
            // 🔥 Также удаляем из requests!
            _requests.value = _requests.value.filter { it.opponentKey != opponentKey }
            // 🔥 Обновляем счетчик requests
            _requestsCount.value = _requests.value.size

            // Вычисляем правильный dialog_key
            val dialogKey =
                    if (currentAccount == opponentKey) {
                        currentAccount // 📁 Saved Messages
                    } else if (currentAccount < opponentKey) {
                        "$currentAccount:$opponentKey"
                    } else {
                        "$opponentKey:$currentAccount"
                    }

            // 🗑️ 1. Очищаем ВСЕ кэши сообщений
            MessageRepository.getInstance(getApplication()).clearDialogCache(opponentKey)
            // 🗑️ Очищаем кэш ChatViewModel
            ChatViewModel.clearCacheForOpponent(opponentKey)

            // 🗑️ 2. Проверяем сколько сообщений в БД до удаления
            val messageCountBefore =
                    database.messageDao().getMessageCount(currentAccount, dialogKey)

            // 🗑️ 3. Удаляем все сообщения из диалога по dialog_key
            val deletedByDialogKey =
                    database.messageDao()
                            .deleteDialog(account = currentAccount, dialogKey = dialogKey)

            // 🗑️ 4. Также удаляем по from/to ключам (на всякий случай - старые сообщения)
            val deletedBetweenUsers =
                    database.messageDao()
                            .deleteMessagesBetweenUsers(
                                    account = currentAccount,
                                    user1 = opponentKey,
                                    user2 = currentAccount
                            )

            // 🗑️ 5. Проверяем сколько сообщений осталось
            val messageCountAfter = database.messageDao().getMessageCount(currentAccount, dialogKey)

            // 🗑️ 6. Удаляем диалог из таблицы dialogs
            database.dialogDao().deleteDialog(account = currentAccount, opponentKey = opponentKey)

            // 🗑️ 7. Проверяем что диалог удален
            val dialogAfter = database.dialogDao().getDialog(currentAccount, opponentKey)
        } catch (e: Exception) {
            // В случае ошибки - возвращаем диалог обратно (откатываем оптимистичное обновление)
            // Flow обновится автоматически из БД
        }
    }

    /** Выйти из группы и удалить локальный групповой диалог */
    suspend fun leaveGroup(groupPublicKey: String): Boolean {
        if (currentAccount.isEmpty()) return false
        if (!isGroupKey(groupPublicKey)) return false

        return try {
            val left = groupRepository.leaveGroup(currentAccount, groupPublicKey)
            if (left) {
                _dialogs.value = _dialogs.value.filter { it.opponentKey != groupPublicKey }
                _requests.value = _requests.value.filter { it.opponentKey != groupPublicKey }
                _requestsCount.value = _requests.value.size
                MessageRepository.getInstance(getApplication()).clearDialogCache(groupPublicKey)
                ChatViewModel.clearCacheForOpponent(groupPublicKey)
            }
            left
        } catch (_: Exception) {
            false
        }
    }

    /** Заблокировать пользователя */
    suspend fun blockUser(publicKey: String) {
        if (currentAccount.isEmpty()) return

        try {
            database.blacklistDao()
                    .blockUser(
                            com.rosetta.messenger.database.BlacklistEntity(
                                    publicKey = publicKey,
                                    account = currentAccount
                            )
                    )
        } catch (e: Exception) {}
    }

    /** Разблокировать пользователя */
    suspend fun unblockUser(publicKey: String) {
        if (currentAccount.isEmpty()) return

        try {
            database.blacklistDao().unblockUser(publicKey, currentAccount)
        } catch (e: Exception) {}
    }

    /**
     * 📬 Загрузить информацию о пользователе для request 📁 НЕ загружаем для Saved Messages (свой
     * publicKey)
     */
    private fun loadUserInfoForRequest(publicKey: String) {
        loadUserInfoForDialog(publicKey)
    }

    /**
     * 🔥 Загрузить информацию о пользователе для диалога 📁 НЕ загружаем для Saved Messages (свой
     * publicKey)
     */
    private fun loadUserInfoForDialog(publicKey: String) {
        // 📁 Не запрашиваем информацию о самом себе (Saved Messages)
        if (publicKey == currentAccount || isGroupKey(publicKey)) {
            return
        }

        // 🔥 Не запрашиваем если уже запрашивали
        if (requestedUserInfoKeys.contains(publicKey)) {
            return
        }
        requestedUserInfoKeys.add(publicKey)

        viewModelScope.launch(Dispatchers.IO) {
            try {
                val sharedPrefs =
                        getApplication<Application>()
                                .getSharedPreferences("rosetta", Application.MODE_PRIVATE)
                val currentUserPrivateKey = sharedPrefs.getString("private_key", "") ?: ""

                if (currentUserPrivateKey.isEmpty()) return@launch

                // 🔥 ВАЖНО: Используем хеш ключа, как в MessageRepository.requestUserInfo
                val privateKeyHash = CryptoManager.generatePrivateKeyHash(currentUserPrivateKey)

                // Запрашиваем информацию о пользователе с сервера
                val packet =
                        PacketSearch().apply {
                            this.privateKey = privateKeyHash
                            this.search = publicKey
                        }
                ProtocolManager.send(packet)
            } catch (e: Exception) {}
        }
    }

    /** Проверить заблокирован ли пользователь */
    suspend fun isUserBlocked(publicKey: String): Boolean {
        if (currentAccount.isEmpty()) return false

        return try {
            database.blacklistDao().isUserBlocked(publicKey, currentAccount)
        } catch (e: Exception) {
            false
        }
    }
}
