package com.seven.ikar.kotlin.feature.chats

import android.Manifest
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.seven.ikar.kotlin.app.AppContainer
import com.seven.ikar.kotlin.core.contacts.PhoneNumberNormalizer
import com.seven.ikar.kotlin.core.model.AndroidAppReleaseDto
import com.seven.ikar.kotlin.core.model.AuthSessionDto
import com.seven.ikar.kotlin.core.model.ChatStateRequest
import com.seven.ikar.kotlin.core.model.ChatSummaryDto
import com.seven.ikar.kotlin.core.model.ChatType
import com.seven.ikar.kotlin.core.model.StoryDto
import com.seven.ikar.kotlin.feature.share.PendingExternalShare
import com.seven.ikar.kotlin.ui.formatLastActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter

data class ChatListItemUiState(
    val chat: ChatSummaryDto,
    val resolvedTitle: String,
    val lastActivityLabel: String,
    val isSelected: Boolean
)

data class PendingShareNavigationTarget(
    val chatId: String,
    val messageId: String?,
    val scrollToLatest: Boolean,
    val requestId: Long = System.nanoTime()
)

enum class ChatListFilter {
    All,
    Unread,
    Pinned,
    Archive
}

data class ChatsUiState(
    val session: AuthSessionDto? = null,
    val query: String = "",
    val filter: ChatListFilter = ChatListFilter.All,
    val chats: List<ChatListItemUiState> = emptyList(),
    val stories: List<StoryDto> = emptyList(),
    val isLoading: Boolean = false,
    val errorMessage: String? = null,
    val latestRelease: AndroidAppReleaseDto? = null,
    val pendingShare: PendingExternalShare? = null,
    val pendingShareNavigation: PendingShareNavigationTarget? = null,
    val isSendingShare: Boolean = false,
    val selectedChatIds: Set<String> = emptySet(),
    val isDeleteDialogVisible: Boolean = false,
    val deleteForEveryone: Boolean = false,
    val showDeleteForEveryoneOption: Boolean = false,
    val isDeletingSelected: Boolean = false
) {
    val isShareMode: Boolean
        get() = pendingShare != null

    val isSelectionMode: Boolean
        get() = selectedChatIds.isNotEmpty()

    val selectedChatsCount: Int
        get() = selectedChatIds.size
}

class ChatsViewModel(private val container: AppContainer) : ViewModel() {
    private val _uiState = MutableStateFlow(ChatsUiState(session = container.sessionStore.read()))
    private val _allChats = mutableListOf<ChatSummaryDto>()
    private var contactNamesByNormalizedPhone: Map<String, String> = emptyMap()
    private var refreshJob: Job? = null
    val uiState: StateFlow<ChatsUiState> = _uiState.asStateFlow()

    init {
        attachIncomingShare()
        attachRealtime()
        loadCachedChats()
        refresh()
        refreshLatestRelease()
    }

    fun updateQuery(value: String) {
        _uiState.value = _uiState.value.copy(query = value)
        applyFilter()
    }

    fun updateFilter(value: ChatListFilter) {
        _uiState.value = _uiState.value.copy(filter = value)
        applyFilter()
    }

    fun handleChatSelectionTap(chat: ChatSummaryDto) {
        val selectedChatIds = _uiState.value.selectedChatIds
        val nextSelectedChatIds = if (selectedChatIds.contains(chat.id)) {
            selectedChatIds - chat.id
        } else {
            selectedChatIds + chat.id
        }

        _uiState.value = _uiState.value.copy(selectedChatIds = nextSelectedChatIds)
        applyFilter()
    }

    fun handleChatLongPress(chat: ChatSummaryDto) {
        if (_uiState.value.selectedChatIds.contains(chat.id)) {
            return
        }

        _uiState.value = _uiState.value.copy(selectedChatIds = _uiState.value.selectedChatIds + chat.id)
        applyFilter()
    }

    fun clearSelection() {
        _uiState.value = _uiState.value.copy(
            selectedChatIds = emptySet(),
            isDeleteDialogVisible = false,
            deleteForEveryone = false,
            showDeleteForEveryoneOption = false,
            isDeletingSelected = false
        )
        applyFilter()
    }

    fun showDeleteDialog() {
        val state = _uiState.value
        if (state.isShareMode || state.selectedChatIds.isEmpty() || state.isDeletingSelected) {
            return
        }

        val showDeleteForEveryoneOption = canDeleteSelectedForEveryone(state.selectedChatIds)
        _uiState.value = state.copy(
            isDeleteDialogVisible = true,
            deleteForEveryone = state.deleteForEveryone && showDeleteForEveryoneOption,
            showDeleteForEveryoneOption = showDeleteForEveryoneOption
        )
    }

    fun dismissDeleteDialog() {
        _uiState.value = _uiState.value.copy(
            isDeleteDialogVisible = false,
            deleteForEveryone = false
        )
    }

    fun updateDeleteForEveryone(value: Boolean) {
        if (!_uiState.value.showDeleteForEveryoneOption) {
            return
        }

        _uiState.value = _uiState.value.copy(deleteForEveryone = value)
    }

    fun confirmDeleteSelected() {
        val state = _uiState.value
        if (state.isShareMode || state.selectedChatIds.isEmpty() || state.isDeletingSelected) {
            return
        }

        val selectedChats = _allChats.filter { state.selectedChatIds.contains(it.id) }
        if (selectedChats.isEmpty()) {
            clearSelection()
            return
        }

        val deleteForEveryone = state.deleteForEveryone && state.showDeleteForEveryoneOption

        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(isDeletingSelected = true, errorMessage = null)
            runCatching {
                container.executeAuthorized { session ->
                    selectedChats.forEach { chat ->
                        container.apiClient.deleteChat(
                            accessToken = session.accessToken,
                            chatId = chat.id,
                            forEveryone = deleteForEveryone
                        )
                    }
                }
            }.onSuccess {
                clearSelection()
                refresh()
            }.onFailure { error ->
                _uiState.value = _uiState.value.copy(isDeletingSelected = false, errorMessage = error.message)
            }
        }
    }

    fun togglePinSelectedChats() {
        val selectedChats = _allChats.filter { _uiState.value.selectedChatIds.contains(it.id) }
        if (selectedChats.isEmpty()) return
        val shouldPin = selectedChats.any { !it.isPinned }
        updateSelectedChatState(ChatStateRequest(isPinned = shouldPin))
    }

    fun archiveSelectedChats() {
        val selectedChats = _allChats.filter { _uiState.value.selectedChatIds.contains(it.id) }
        if (selectedChats.isEmpty()) return
        val shouldArchive = selectedChats.any { !it.isArchived }
        updateSelectedChatState(ChatStateRequest(isArchived = shouldArchive))
    }

    fun muteSelectedChats() {
        val selectedChats = _allChats.filter { _uiState.value.selectedChatIds.contains(it.id) }
        if (selectedChats.isEmpty()) return
        val shouldMute = selectedChats.any { !it.isMuted }
        updateSelectedChatState(
            ChatStateRequest(
                mutedUntil = if (shouldMute) {
                    OffsetDateTime.now(ZoneOffset.UTC).plusYears(1).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
                } else {
                    null
                },
                isMuted = shouldMute
            )
        )
    }

    private fun updateSelectedChatState(request: ChatStateRequest) {
        val selectedChatIds = _uiState.value.selectedChatIds.toList()
        if (selectedChatIds.isEmpty()) {
            return
        }

        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(errorMessage = null)
            runCatching {
                container.executeAuthorized { session ->
                    selectedChatIds.forEach { chatId ->
                        container.apiClient.updateChatState(session.accessToken, chatId, request)
                    }
                }
            }.onSuccess {
                clearSelection()
                refresh()
            }.onFailure { error ->
                _uiState.value = _uiState.value.copy(errorMessage = error.message)
            }
        }
    }

    fun refresh() {
        if (container.sessionStore.read() == null) {
            _allChats.clear()
            contactNamesByNormalizedPhone = emptyMap()
            _uiState.value = _uiState.value.copy(session = null, chats = emptyList(), stories = emptyList(), selectedChatIds = emptySet())
            return
        }

        viewModelScope.launch {
            val currentSession = container.sessionStore.read()
            _uiState.value = _uiState.value.copy(session = currentSession, isLoading = true, errorMessage = null)
            runCatching {
                container.executeAuthorized { session ->
                    container.realtimeService.connect(session.accessToken)
                    val chats = container.apiClient.getChats(session.accessToken)
                    val stories = runCatching { container.apiClient.getStories(session.accessToken) }.getOrDefault(emptyList())
                    Triple(session, chats, stories)
                }
            }.onSuccess { result ->
                updateContactNames(loadDeviceContactNames())
                _allChats.clear()
                _allChats.addAll(result.second)
                cacheChatList(result.first.user.id, result.second)
                _uiState.value = _uiState.value.copy(
                    session = result.first,
                    stories = result.third.sortedByDescending { it.createdAt },
                    isLoading = false,
                    errorMessage = null
                )
                applyFilter()
            }.onFailure { error ->
                updateContactNames(loadDeviceContactNames())
                _uiState.value = _uiState.value.copy(isLoading = false, errorMessage = error.message)
                applyFilter()
            }
        }
    }

    fun logout() {
        container.realtimeService.disconnect()
        viewModelScope.launch {
            runCatching { container.pushRegistrationManager.unregisterCurrentDeviceAsync() }
        }
        container.incomingShareStore.clear()
        container.sessionStore.clear()
        _allChats.clear()
        contactNamesByNormalizedPhone = emptyMap()
        _uiState.value = ChatsUiState()
    }

    fun sendShareToChat(chat: ChatSummaryDto) {
        if (_uiState.value.pendingShare == null) {
            return
        }

        sendShare(chatIds = listOf(chat.id), navigateToChatId = chat.id)
    }

    fun sendShareToSelectedChats() {
        val state = _uiState.value
        if (state.pendingShare == null || state.selectedChatIds.isEmpty()) {
            return
        }

        sendShare(chatIds = state.selectedChatIds.toList(), navigateToChatId = null)
    }

    fun dismissPendingShare() {
        container.incomingShareStore.clear()
        _uiState.value = _uiState.value.copy(
            errorMessage = null,
            isSendingShare = false,
            selectedChatIds = emptySet()
        )
        applyFilter()
    }

    fun consumePendingShareNavigation() {
        _uiState.value = _uiState.value.copy(pendingShareNavigation = null)
    }

    private fun refreshLatestRelease() {
        viewModelScope.launch {
            runCatching { container.apiClient.getLatestAndroidRelease() }
                .onSuccess { release -> _uiState.value = _uiState.value.copy(latestRelease = release) }
        }
    }

    private fun loadCachedChats() {
        val session = container.sessionStore.read() ?: return
        viewModelScope.launch {
            val cachedContactNames = loadDeviceContactNames()
            val cachedChats = withContext(Dispatchers.IO) {
                runCatching { container.chatListCache.loadChats(session.user.id) }.getOrDefault(emptyList())
            }
            if (cachedChats.isEmpty() || _allChats.isNotEmpty()) {
                return@launch
            }

            updateContactNames(cachedContactNames)
            _allChats.clear()
            _allChats.addAll(cachedChats)
            _uiState.value = _uiState.value.copy(session = session)
            applyFilter()
        }
    }

    private fun cacheChatList(ownerUserId: String, chats: List<ChatSummaryDto>) {
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { container.chatListCache.replaceChats(ownerUserId, chats) }
        }
    }

    private fun updateContactNames(contactNames: Map<String, String>) {
        if (contactNames.isNotEmpty() || contactNamesByNormalizedPhone.isEmpty()) {
            contactNamesByNormalizedPhone = contactNames
        }
    }

    private fun applyFilter() {
        val currentState = _uiState.value
        val query = currentState.query.trim()
        val existingChatIds = _allChats.mapTo(linkedSetOf(), ChatSummaryDto::id)
        val selectedChatIds = currentState.selectedChatIds.intersect(existingChatIds)
        val showDeleteForEveryoneOption = if (currentState.isShareMode) {
            false
        } else {
            canDeleteSelectedForEveryone(selectedChatIds)
        }
        val deleteForEveryone = !currentState.isShareMode &&
            currentState.deleteForEveryone &&
            showDeleteForEveryoneOption
        val chatItems = _allChats
            .asSequence()
            .filter { chat ->
                when (currentState.filter) {
                    ChatListFilter.All -> !chat.isArchived
                    ChatListFilter.Unread -> chat.unreadCount > 0 && !chat.isArchived
                    ChatListFilter.Pinned -> chat.isPinned
                    ChatListFilter.Archive -> chat.isArchived
                }
            }
            .sortedWith(
                compareByDescending<ChatSummaryDto> { it.isPinned }
                    .thenByDescending { it.lastActivityAt.orEmpty() }
            )
            .map { chat -> toChatListItem(chat, selectedChatIds) }
            .toList()
        val filtered = if (query.isBlank()) {
            chatItems
        } else {
            chatItems.filter { item ->
                item.resolvedTitle.contains(query, ignoreCase = true) ||
                    item.chat.title.contains(query, ignoreCase = true) ||
                    item.chat.secondaryText.orEmpty().contains(query, ignoreCase = true) ||
                    item.chat.lastMessagePreview.orEmpty().contains(query, ignoreCase = true)
            }
        }
        _uiState.value = currentState.copy(
            chats = filtered,
            selectedChatIds = selectedChatIds,
            showDeleteForEveryoneOption = showDeleteForEveryoneOption,
            deleteForEveryone = deleteForEveryone,
            isDeleteDialogVisible = !currentState.isShareMode &&
                currentState.isDeleteDialogVisible &&
                selectedChatIds.isNotEmpty(),
            isDeletingSelected = !currentState.isShareMode &&
                currentState.isDeletingSelected &&
                selectedChatIds.isNotEmpty()
        )
    }

    private fun toChatListItem(chat: ChatSummaryDto, selectedChatIds: Set<String>): ChatListItemUiState {
        val currentUserId = _uiState.value.session?.user?.id
        return ChatListItemUiState(
            chat = chat,
            resolvedTitle = resolveChatTitle(chat, currentUserId),
            lastActivityLabel = formatLastActivity(chat.lastActivityAt),
            isSelected = selectedChatIds.contains(chat.id)
        )
    }

    private fun resolveChatTitle(chat: ChatSummaryDto, currentUserId: String?): String {
        if (chat.type != ChatType.Direct) {
            return chat.title
        }

        val peer = chat.participants.firstOrNull { it.id != currentUserId } ?: chat.participants.firstOrNull()
        val normalizedPhone = PhoneNumberNormalizer.normalize(peer?.phoneNumber)
        val contactName = normalizedPhone?.let(contactNamesByNormalizedPhone::get)
        return contactName?.takeIf { it.isNotBlank() }
            ?: peer?.displayName?.takeIf { it.isNotBlank() }
            ?: chat.title
    }

    private suspend fun loadDeviceContactNames(): Map<String, String> = withContext(Dispatchers.IO) {
        if (!hasContactsPermission()) {
            return@withContext emptyMap()
        }

        runCatching {
            container.contactDiscoveryService
                .getContacts()
                .associate { contact -> contact.normalizedPhoneNumber to contact.displayName }
        }.getOrDefault(emptyMap())
    }

    private fun hasContactsPermission(): Boolean =
        ContextCompat.checkSelfPermission(
            container.appContext,
            Manifest.permission.READ_CONTACTS
        ) == PackageManager.PERMISSION_GRANTED

    private fun canDeleteSelectedForEveryone(selectedChatIds: Set<String>): Boolean {
        if (selectedChatIds.size != 1) {
            return false
        }

        val chat = _allChats.firstOrNull { selectedChatIds.contains(it.id) } ?: return false
        return chat.type == ChatType.Direct
    }

    private fun attachRealtime() {
        viewModelScope.launch {
            container.realtimeService.chatCreated.collect { scheduleRefresh() }
        }
        viewModelScope.launch {
            container.realtimeService.chatsChanged.collect { scheduleRefresh() }
        }
        viewModelScope.launch {
            container.realtimeService.messagesCreated.collect { scheduleRefresh() }
        }
        viewModelScope.launch {
            container.realtimeService.messagesUpdated.collect { scheduleRefresh() }
        }
        viewModelScope.launch {
            container.realtimeService.appUpdateAvailable.collect { release ->
                _uiState.value = _uiState.value.copy(latestRelease = release)
            }
        }
    }

    private fun scheduleRefresh() {
        refreshJob?.cancel()
        refreshJob = viewModelScope.launch {
            delay(400)
            refresh()
        }
    }

    private fun attachIncomingShare() {
        viewModelScope.launch {
            container.incomingShareStore.pendingShare.collect { pendingShare ->
                val currentState = _uiState.value
                _uiState.value = currentState.copy(
                    pendingShare = pendingShare,
                    selectedChatIds = if (pendingShare == null) emptySet() else currentState.selectedChatIds,
                    isDeleteDialogVisible = if (pendingShare == null) currentState.isDeleteDialogVisible else false,
                    deleteForEveryone = if (pendingShare == null) currentState.deleteForEveryone else false,
                    showDeleteForEveryoneOption = if (pendingShare == null) currentState.showDeleteForEveryoneOption else false,
                    isDeletingSelected = if (pendingShare == null) currentState.isDeletingSelected else false,
                    isSendingShare = currentState.isSendingShare && pendingShare != null
                )
                applyFilter()
            }
        }
    }

    private fun sendShare(chatIds: List<String>, navigateToChatId: String?) {
        val state = _uiState.value
        val pendingShare = state.pendingShare ?: return
        if (chatIds.isEmpty() || state.isSendingShare) {
            return
        }

        viewModelScope.launch {
            _uiState.value = state.copy(isSendingShare = true, errorMessage = null)
            runCatching {
                container.executeAuthorized { session ->
                    var navigatedMessageId: String? = null
                    chatIds.forEach { chatId ->
                        val messageId = sendPendingShare(
                            accessToken = session.accessToken,
                            chatId = chatId,
                            pendingShare = pendingShare
                        )
                        if (chatId == navigateToChatId) {
                            navigatedMessageId = messageId
                        }
                    }
                    navigatedMessageId
                }
            }.onSuccess { navigatedMessageId ->
                _uiState.value = _uiState.value.copy(
                    isSendingShare = false,
                    errorMessage = null,
                    pendingShareNavigation = navigateToChatId?.let { chatId ->
                        PendingShareNavigationTarget(
                            chatId = chatId,
                            messageId = navigatedMessageId,
                            scrollToLatest = navigatedMessageId == null
                        )
                    },
                    selectedChatIds = emptySet()
                )
                container.incomingShareStore.clear()
                refresh()
            }.onFailure { error ->
                _uiState.value = _uiState.value.copy(
                    isSendingShare = false,
                    errorMessage = error.message
                )
            }
        }
    }

    private suspend fun sendPendingShare(
        accessToken: String,
        chatId: String,
        pendingShare: PendingExternalShare
    ): String? {
        val sharedText = pendingShare.text?.trim().orEmpty()
        if (pendingShare.attachments.isEmpty()) {
            check(sharedText.isNotBlank()) { "Нечего отправлять." }
            return container.apiClient.sendMessage(chatId, sharedText, accessToken).id
        }

        container.transferQueueRepository.enqueueUpload(
            chatId = chatId,
            text = sharedText.takeIf { it.isNotBlank() },
            replyToMessageId = null,
            files = pendingShare.attachments
        )
        return null
    }
}
