package com.github.libretube.ui.models

import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.libretube.api.MediaServiceRepository
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.SubscriptionHelper
import com.github.libretube.api.TrendingCategory
import com.github.libretube.api.obj.Playlists
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.db.DatabaseHelper
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.db.obj.PlaylistBookmark
import com.github.libretube.extensions.runSafely
import com.github.libretube.extensions.updateIfChanged
import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.PreferenceHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class HomeViewModel : ViewModel() {
    enum class HomeFilter {
        ALL,
        VIDEOS,
        MIXES,
        LIVE,
        PODCASTS
    }

    private val hideWatched
        get() = PreferenceHelper.getBoolean(
            PreferenceKeys.HIDE_WATCHED_FROM_FEED,
            false
        )
    private val showUpcoming
        get() = PreferenceHelper.getBoolean(
            PreferenceKeys.SHOW_UPCOMING_IN_FEED,
            true
        )

    val recommendations: MutableLiveData<List<StreamItem>> = MutableLiveData(null)
    val feed: MutableLiveData<List<StreamItem>> = MutableLiveData(null)
    val bookmarks: MutableLiveData<List<PlaylistBookmark>> = MutableLiveData(null)
    val playlists: MutableLiveData<List<Playlists>> = MutableLiveData(null)
    val continueWatching: MutableLiveData<List<StreamItem>> = MutableLiveData(null)
    val selectedFilter: MutableLiveData<HomeFilter> = MutableLiveData(HomeFilter.ALL)
    val isLoading: MutableLiveData<Boolean> = MutableLiveData(true)
    val loadedSuccessfully: MutableLiveData<Boolean> = MutableLiveData(false)

    var loadedRegion: String? = null
        private set

    private var loadHomeJob: Job? = null
    private var recommendationNextPage: String? = null
    private var recommendationSearchQuery: String? = null
    private var recommendationSearchFilter: String? = null
    private var isLoadingRecommendationsMore = false
    private val recommendationsByFilter = mutableMapOf<HomeFilter, List<StreamItem>>()

    fun setFilter(filter: HomeFilter) {
        if (selectedFilter.value == filter) return
        selectedFilter.value = filter
        // Show the last successful list for this chip immediately (YouTube-like tab switching)
        // while a fresh/cache-backed request updates it in the background.
        recommendations.value = recommendationsByFilter[filter].orEmpty()
        loadedSuccessfully.value = !recommendations.value.isNullOrEmpty()
        recommendationNextPage = null
        recommendationSearchQuery = null
        recommendationSearchFilter = null
    }

    fun loadHomeFeed(
        context: Context,
        subscriptionsViewModel: SubscriptionsViewModel,
        visibleItems: Set<String>,
        forceRefresh: Boolean = false,
        onUnusualLoadTime: () -> Unit
    ) {
        isLoading.value = true
        loadHomeJob?.cancel()

        loadHomeJob = viewModelScope.launch {
            val filter = selectedFilter.value ?: HomeFilter.ALL
            val result = async {
                awaitAll(
                    async { loadRecommendations(context, filter, forceRefresh) },
                    // Personal rows belong to "Todos" and should not slow down category switches.
                    async { if (filter == HomeFilter.ALL) loadFeed(subscriptionsViewModel) },
                    async {
                        if (filter == HomeFilter.ALL && visibleItems.contains(BOOKMARKS)) {
                            loadBookmarks()
                        }
                    },
                    async {
                        if (filter == HomeFilter.ALL && visibleItems.contains(PLAYLISTS)) {
                            loadPlaylists()
                        }
                    },
                    async { if (filter == HomeFilter.ALL) loadVideosToContinueWatching() }
                )
                loadedRegion = PreferenceHelper.getTrendingRegion(context)
                loadedSuccessfully.value = if (filter == HomeFilter.ALL) {
                    !recommendations.value.isNullOrEmpty() ||
                        !feed.value.isNullOrEmpty() ||
                        !bookmarks.value.isNullOrEmpty() ||
                        !playlists.value.isNullOrEmpty() ||
                        !continueWatching.value.isNullOrEmpty()
                } else {
                    !recommendations.value.isNullOrEmpty()
                }
                isLoading.value = false
            }

            withContext(Dispatchers.IO) {
                delay(UNUSUAL_LOAD_TIME_MS)
                if (result.isActive) onUnusualLoadTime.invoke()
            }
        }
    }

    private suspend fun loadRecommendations(
        context: Context,
        filter: HomeFilter,
        forceRefresh: Boolean
    ) {
        val region = PreferenceHelper.getTrendingRegion(context)
        recommendationNextPage = null
        recommendationSearchQuery = null
        recommendationSearchFilter = null
        isLoadingRecommendationsMore = false

        runSafely(
            onSuccess = { videos ->
                recommendationsByFilter[filter] = videos
                // Ignore a late result belonging to a chip the user has already left.
                if (selectedFilter.value == filter) recommendations.updateIfChanged(videos)
            },
            ioBlock = { discoverStreams(region, filter, forceRefresh) }
        )
    }

    private suspend fun discoverStreams(
        region: String,
        filter: HomeFilter,
        forceRefresh: Boolean
    ): List<StreamItem> {
        val repository = if (forceRefresh) {
            MediaServiceRepository.uncachedInstance
        } else {
            MediaServiceRepository.instance
        }

        val historyQueries = DatabaseHolder.Database.searchHistoryDao()
            .getAll()
            .mapNotNull { it.query.trim().takeIf(String::isNotBlank) }
            .distinct()
            .take(12)

        return when (filter) {
            HomeFilter.ALL -> {
                val query = buildGeneralDiscoveryQuery(historyQueries)
                loadSearchResults(repository, query, "videos")
                    .ifEmpty {
                        repository.getTrending(region, TrendingCategory.MUSIC)
                            .filter(::isPlayableStream)
                    }
            }

            HomeFilter.VIDEOS -> {
                val query = buildVideoDiscoveryQuery(historyQueries)
                loadSearchResults(repository, query, "videos")
                    .ifEmpty {
                        repository.getTrending(region, TrendingCategory.TRAILERS)
                            .filter(::isPlayableStream)
                    }
            }

            HomeFilter.MIXES -> {
                val query = buildMixDiscoveryQuery(historyQueries)
                loadSearchResults(repository, query, "music_videos")
                    .ifEmpty {
                        loadSearchResults(repository, "$query mix", "videos")
                    }
            }

            HomeFilter.LIVE -> {
                val interest = historyQueries.firstOrNull().orEmpty()
                loadSearchResults(
                    repository,
                    "$interest live now livestream".trim(),
                    "videos"
                ).filter { it.isLive }
                    .ifEmpty {
                        repository.getTrending(region, TrendingCategory.LIVE)
                            .filter { isPlayableStream(it) && it.isLive }
                    }
            }

            HomeFilter.PODCASTS -> {
                val interest = historyQueries.firstOrNull().orEmpty()
                loadSearchResults(
                    repository,
                    "$interest podcast interview".trim(),
                    "videos"
                ).ifEmpty {
                    repository.getTrending(region, TrendingCategory.PODCASTS)
                        .filter(::isPlayableStream)
                }
            }
        }.distinctBy { it.url }
    }

    private suspend fun loadSearchResults(
        repository: MediaServiceRepository,
        searchQuery: String,
        filter: String
    ): List<StreamItem> {
        if (searchQuery.isBlank()) return emptyList()

        val searchResult = runCatching {
            repository.getSearchResults(searchQuery, filter)
        }.getOrNull() ?: return emptyList()

        val streams = searchResult.items
            .filter { item ->
                (item.type == StreamItem.TYPE_STREAM || item.type == "video") &&
                    !item.title.isNullOrBlank()
            }
            .map { it.toStreamItem() }
            .filter(::isPlayableStream)
            .distinctBy { it.url }

        if (streams.isNotEmpty()) {
            recommendationSearchQuery = searchQuery
            recommendationSearchFilter = filter
            recommendationNextPage = searchResult.nextpage
        }

        return streams
    }

    private suspend fun loadMoreSearchResults(): List<StreamItem> {
        val nextPage = recommendationNextPage ?: return emptyList()
        val query = recommendationSearchQuery ?: return emptyList()
        val filter = recommendationSearchFilter ?: return emptyList()

        val searchResult = runCatching {
            MediaServiceRepository.instance.getSearchResultsNextPage(query, filter, nextPage)
        }.getOrNull() ?: return emptyList()

        recommendationNextPage = searchResult.nextpage
        return searchResult.items
            .filter { item ->
                (item.type == StreamItem.TYPE_STREAM || item.type == "video") &&
                    !item.title.isNullOrBlank()
            }
            .map { it.toStreamItem() }
            .filter(::isPlayableStream)
            .distinctBy { it.url }
    }

    private fun isPlayableStream(stream: StreamItem): Boolean =
        !stream.url.isNullOrBlank() && !stream.title.isNullOrBlank() && !stream.isShort

    private fun buildGeneralDiscoveryQuery(historyQueries: List<String>): String {
        // Keep discovery keys stable so returning Home can reuse the device/API cache instead of
        // turning every visit into a brand-new request. Search history is already relevance/recent
        // ordered by the DAO, so the first terms are the best personalization signal.
        if (historyQueries.isNotEmpty()) {
            return historyQueries.take(3).joinToString(" ")
        }
        return "popular documentary music"
    }

    private fun buildVideoDiscoveryQuery(historyQueries: List<String>): String {
        val interest = historyQueries.take(2).joinToString(" ")
        return if (interest.isBlank()) "popular videos" else "$interest videos"
    }

    private fun buildMixDiscoveryQuery(historyQueries: List<String>): String {
        val interest = historyQueries.firstOrNull().orEmpty()
        return if (interest.isBlank()) "music mix playlist" else "$interest mix music"
    }

    private suspend fun loadFeed(subscriptionsViewModel: SubscriptionsViewModel) {
        runSafely(
            onSuccess = { videos -> feed.updateIfChanged(videos) },
            ioBlock = { tryLoadFeed(subscriptionsViewModel) }
        )
    }

    private suspend fun loadBookmarks() {
        runSafely(
            onSuccess = { newBookmarks -> bookmarks.updateIfChanged(newBookmarks) },
            ioBlock = { DatabaseHolder.Database.playlistBookmarkDao().getAll() }
        )
    }

    private suspend fun loadPlaylists() {
        runSafely(
            onSuccess = { newPlaylists -> playlists.updateIfChanged(newPlaylists) },
            ioBlock = { PlaylistsHelper.getPlaylists() }
        )
    }

    private suspend fun loadVideosToContinueWatching() {
        if (!PlayerHelper.watchHistoryEnabled) return
        runSafely(
            onSuccess = { videos -> continueWatching.updateIfChanged(videos) },
            ioBlock = ::loadWatchingFromDB
        )
    }

    private suspend fun loadWatchingFromDB(): List<StreamItem> {
        val videos = DatabaseHelper.getWatchHistoryPage(1, 20)
        return DatabaseHelper.filterUnwatched(videos.map { it.toStreamItem() })
    }

    private suspend fun tryLoadFeed(subscriptionsViewModel: SubscriptionsViewModel): List<StreamItem> {
        val feed = subscriptionsViewModel.videoFeed.value ?: run {
            SubscriptionHelper.getFeed(forceRefresh = false).also {
                subscriptionsViewModel.videoFeed.postValue(it)
            }
        }

        return DatabaseHelper.filterByStreamTypeAndWatchPosition(feed, hideWatched, showUpcoming)
    }

    fun loadMoreRecommendations() {
        if (recommendationNextPage.isNullOrBlank() || isLoadingRecommendationsMore) return

        val filterAtStart = selectedFilter.value
        val queryAtStart = recommendationSearchQuery
        isLoadingRecommendationsMore = true
        viewModelScope.launch {
            try {
                val current = recommendations.value.orEmpty()
                val additional = loadMoreSearchResults().let { streams ->
                    if (filterAtStart == HomeFilter.LIVE) streams.filter { it.isLive }
                    else streams
                }

                // Ignore a late page from a tab the user has already left.
                if (selectedFilter.value != filterAtStart || recommendationSearchQuery != queryAtStart) {
                    return@launch
                }
                if (additional.isNotEmpty()) {
                    val updated = (current + additional).distinctBy { it.url }
                    filterAtStart?.let { recommendationsByFilter[it] = updated }
                    recommendations.updateIfChanged(updated)
                }
            } finally {
                isLoadingRecommendationsMore = false
            }
        }
    }

    companion object {
        private const val UNUSUAL_LOAD_TIME_MS = 10_000L
        private const val BOOKMARKS = "bookmarks"
        private const val PLAYLISTS = "playlists"
    }
}
