package com.seven.ikar.kotlin.feature.search

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.seven.ikar.kotlin.app.AppContainer
import com.seven.ikar.kotlin.core.model.ChatType
import com.seven.ikar.kotlin.core.model.ChatSearchResultDto
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
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

enum class GlobalSearchFilter(val wireValue: String?) {
    All(null),
    Media("media"),
    Files("files"),
    Links("links")
}

enum class GlobalSearchDateFilter {
    Anytime,
    Today,
    ThisWeek,
    ThisMonth
}

enum class GlobalSearchResultType {
    All,
    Chats,
    Messages,
    Attachments,
    Bots,
    GroupCalls
}

data class SearchCapabilityHooks(
    val supportsMediaDateGrouping: Boolean = true,
    val supportsBotMiniAppPlaceholder: Boolean = true,
    val supportsScheduledMessageOptions: Boolean = true,
    val supportsGroupCallPlaceholder: Boolean = true
)

data class GlobalSearchUiState(
    val query: String = "",
    val filter: GlobalSearchFilter = GlobalSearchFilter.All,
    val dateFilter: GlobalSearchDateFilter = GlobalSearchDateFilter.Anytime,
    val resultType: GlobalSearchResultType = GlobalSearchResultType.All,
    val authorFilter: String = "",
    val results: List<ChatSearchResultDto> = emptyList(),
    val page: Int = 1,
    val hasMore: Boolean = false,
    val isSearching: Boolean = false,
    val isLoadingMore: Boolean = false,
    val errorMessage: String? = null,
    val hooks: SearchCapabilityHooks = SearchCapabilityHooks()
) {
    val visibleResults: List<ChatSearchResultDto>
        get() = results.filter { result ->
            result.matchesAuthor(authorFilter) &&
                result.matchesDate(dateFilter) &&
                result.matchesType(resultType)
        }
}

class GlobalSearchViewModel(private val container: AppContainer) : ViewModel() {
    private val _uiState = MutableStateFlow(GlobalSearchUiState())
    val uiState: StateFlow<GlobalSearchUiState> = _uiState.asStateFlow()
    private var searchJob: Job? = null

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

    fun updateFilter(filter: GlobalSearchFilter) {
        _uiState.value = _uiState.value.copy(filter = filter)
        scheduleSearch(immediate = true)
    }

    fun updateDateFilter(filter: GlobalSearchDateFilter) {
        _uiState.value = _uiState.value.copy(dateFilter = filter)
        scheduleSearch(immediate = true)
    }

    fun updateResultType(type: GlobalSearchResultType) {
        _uiState.value = _uiState.value.copy(resultType = type)
        scheduleSearch(immediate = true)
    }

    fun updateAuthorFilter(value: String) {
        _uiState.value = _uiState.value.copy(authorFilter = value)
        scheduleSearch()
    }

    fun refresh() {
        scheduleSearch(immediate = true)
    }

    fun loadMore() {
        val state = _uiState.value
        if (!state.hasMore || state.isSearching || state.isLoadingMore) {
            return
        }
        scheduleSearch(immediate = true, append = true)
    }

    private fun scheduleSearch(immediate: Boolean = false, append: Boolean = false) {
        searchJob?.cancel()
        val state = _uiState.value
        val query = state.query.trim()
        if (query.length < 2 && state.filter == GlobalSearchFilter.All) {
            _uiState.value = state.copy(results = emptyList(), page = 1, hasMore = false, isSearching = false, isLoadingMore = false, errorMessage = null)
            return
        }

        searchJob = viewModelScope.launch {
            if (!immediate) {
                delay(250)
            }
            val requestState = _uiState.value
            val targetPage = if (append) requestState.page + 1 else 1
            val dateRange = requestState.dateFilter.toServerRange()
            _uiState.value = requestState.copy(
                isSearching = !append,
                isLoadingMore = append,
                errorMessage = null
            )
            runCatching {
                container.executeAuthorized { session ->
                    container.apiClient.searchChatsPage(
                        accessToken = session.accessToken,
                        query = requestState.query.trim().ifBlank { " " },
                        filter = requestState.filter.wireValue,
                        type = requestState.resultType.serverType(),
                        author = requestState.authorFilter.trim().takeIf { it.isNotBlank() },
                        dateFrom = dateRange?.first,
                        dateTo = dateRange?.second,
                        page = targetPage,
                        limit = 40
                    )
                }
            }.onSuccess { page ->
                val currentResults = if (append) _uiState.value.results else emptyList()
                _uiState.value = _uiState.value.copy(
                    results = (currentResults + page.results).distinctBy { "${it.chat.id}:${it.message?.id.orEmpty()}:${it.matchText}" },
                    page = page.page,
                    hasMore = page.hasMore,
                    isSearching = false,
                    isLoadingMore = false
                )
            }.onFailure { error ->
                _uiState.value = _uiState.value.copy(isSearching = false, isLoadingMore = false, errorMessage = error.message)
            }
        }
    }
}

private fun GlobalSearchResultType.serverType(): String? =
    when (this) {
        GlobalSearchResultType.Messages -> "text"
        GlobalSearchResultType.Attachments -> "attachments"
        else -> null
    }

private fun GlobalSearchDateFilter.toServerRange(): Pair<String, String>? =
    when (this) {
        GlobalSearchDateFilter.Anytime -> null
        GlobalSearchDateFilter.Today -> dateKey(0).toDayRange()
        GlobalSearchDateFilter.ThisWeek -> offsetDateKey(-6).toDateStart() to todayKey().toDateEnd()
        GlobalSearchDateFilter.ThisMonth -> todayKey().take(8).let { prefix ->
            "${prefix}01".toDateStart() to todayKey().toDateEnd()
        }
    }

private fun String.toDayRange(): Pair<String, String> = toDateStart() to toDateEnd()

private fun String.toDateStart(): String = "${this}T00:00:00Z"

private fun String.toDateEnd(): String = "${this}T23:59:59Z"

private fun ChatSearchResultDto.matchesAuthor(authorFilter: String): Boolean {
    val query = authorFilter.trim()
    if (query.isBlank()) {
        return true
    }

    val sender = message?.sender ?: return false
    return sender.displayName.contains(query, ignoreCase = true) ||
        sender.username.contains(query, ignoreCase = true) ||
        (sender.phoneNumber?.contains(query, ignoreCase = true) ?: false)
}

private fun ChatSearchResultDto.matchesDate(filter: GlobalSearchDateFilter): Boolean {
    if (filter == GlobalSearchDateFilter.Anytime) {
        return true
    }

    val dateKey = searchDateKey() ?: return false
    val today = todayKey()
    return when (filter) {
        GlobalSearchDateFilter.Anytime -> true
        GlobalSearchDateFilter.Today -> dateKey == today
        GlobalSearchDateFilter.ThisWeek -> dateKey >= offsetDateKey(-6) && dateKey <= today
        GlobalSearchDateFilter.ThisMonth -> dateKey.take(7) == today.take(7)
    }
}

private fun ChatSearchResultDto.matchesType(type: GlobalSearchResultType): Boolean =
    when (type) {
        GlobalSearchResultType.All -> true
        GlobalSearchResultType.Chats -> message == null
        GlobalSearchResultType.Messages -> message != null
        GlobalSearchResultType.Attachments -> message?.attachments?.isNotEmpty() == true
        GlobalSearchResultType.Bots -> chat.participants.any { it.isBot }
        GlobalSearchResultType.GroupCalls -> chat.type == ChatType.Group
    }

internal fun ChatSearchResultDto.searchDateGroupLabel(): String =
    searchDateKey() ?: "No date"

private fun ChatSearchResultDto.searchDateKey(): String? =
    (matchedAt ?: message?.sentAt ?: chat.lastActivityAt)?.takeIf { it.length >= 10 }?.take(10)

private fun todayKey(): String = dateKey(0)

private fun offsetDateKey(days: Int): String = dateKey(days)

private fun dateKey(daysFromToday: Int): String {
    val calendar = Calendar.getInstance()
    calendar.add(Calendar.DAY_OF_YEAR, daysFromToday)
    return SimpleDateFormat("yyyy-MM-dd", Locale.US).format(calendar.time)
}
