package org.mosad.teapod.ui.activity.main.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.async
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import org.mosad.teapod.parser.crunchyroll.*
import org.mosad.teapod.util.DataTypes.MediaType
import org.mosad.teapod.util.tmdb.*
import org.mosad.teapod.util.toPlayheadsMap

/**
 * handle media, next ep and tmdb
 * TODO this lives in activity, is this correct?
 */
class MediaFragmentViewModel(application: Application) : AndroidViewModel(application) {

    var seriesCrunchy = NoneSeriesItem // movies are also series
        internal set
    var seasonsCrunchy = NoneSeasons
        internal set
    var currentSeasonCrunchy = NoneSeason
        internal set
    var episodesCrunchy = NoneEpisodes
        internal set
    val currentEpisodesCrunchy = arrayListOf<Episode>() // used for EpisodeItemAdapter (easier updates)

    // additional media info, might change during during user interaction
    // use a map to update the episode adapter values
    val currentPlayheads: MutableMap<String, PlayheadObject> = mutableMapOf()
    var isWatchlist = false
        internal set
    var upNextSeries = NoneUpNextSeriesList
        internal set
    var similarTo = NoneSimilarToResult
        internal set

    // TMDB stuff
    var mediaType = MediaType.OTHER
        internal set
    var tmdbResult: TMDBResult = NoneTMDB // TODO rename
        internal set
    var tmdbTVSeason: TMDBTVSeason = NoneTMDBTVSeason
        internal set

    /**
     * @param crunchyId the crunchyroll series id
     */

    suspend fun loadCrunchy(crunchyId: String) {
        // load series and seasons info in parallel
        listOf(
            viewModelScope.launch { seriesCrunchy = Crunchyroll.series(crunchyId).data.first() },
            viewModelScope.launch { seasonsCrunchy = Crunchyroll.seasons(crunchyId) },
            viewModelScope.launch { isWatchlist = Crunchyroll.isWatchlist(crunchyId) },
            viewModelScope.launch { upNextSeries = Crunchyroll.upNextSeries(crunchyId) },
            viewModelScope.launch { similarTo = Crunchyroll.similarTo(crunchyId) }
        ).joinAll()

        // load the preferred season:
        // next episode > first season
        currentSeasonCrunchy = if (upNextSeries != NoneUpNextSeriesList) {
            seasonsCrunchy.data.firstOrNull{ season ->
                season.id == upNextSeries.data.first().panel.episodeMetadata.seasonId
            } ?: seasonsCrunchy.data.first()
        } else {
            seasonsCrunchy.data.first()
        }

        // Note: if we need to query metaDB, do it now

        // load episodes and metaDB in parallel (tmdb needs mediaType, which is set via episodes)
        viewModelScope.launch { episodesCrunchy = Crunchyroll.episodes(currentSeasonCrunchy.id) }.join()
        currentEpisodesCrunchy.clear()
        currentEpisodesCrunchy.addAll(episodesCrunchy.data)

        // set media type
        mediaType = episodesCrunchy.data.firstOrNull()?.let {
            if (it.episodeNumber != null) MediaType.TVSHOW else MediaType.MOVIE
        } ?: MediaType.OTHER

        // load playheads and tmdb in parallel
        listOf(
            updatePlayheadsAsync(),
            viewModelScope.launch { loadTmdbInfo() } // use tmdb search to get media info
        ).joinAll()
    }

    /**
     * Load the tmdb info for the selected media.
     * The TMDB search return a media type, use this to get the details (movie/tv show and season)
     */
    private suspend fun loadTmdbInfo() {
        val tmdbApiController = TMDBApiController()

        val tmdbSearchResult = when(mediaType) {
            MediaType.MOVIE -> tmdbApiController.searchMovie(seriesCrunchy.title)
            MediaType.TVSHOW -> tmdbApiController.searchTVShow(seriesCrunchy.title)
            else -> NoneTMDBSearch
        }

        tmdbResult = if (tmdbSearchResult.results.isNotEmpty()) {
            when (val result = tmdbSearchResult.results.first()) {
                is TMDBSearchResultMovie -> tmdbApiController.getMovieDetails(result.id)
                is TMDBSearchResultTVShow -> tmdbApiController.getTVShowDetails(result.id)
                else -> NoneTMDB
            }
        } else NoneTMDB

        // currently not used
//        tmdbTVSeason = if (tmdbResult is TMDBTVShow) {
//            tmdbApiController.getTVSeasonDetails(tmdbResult.id, 0)
//        } else NoneTMDBTVSeason
    }

    /**
     * Get current playheads for all episodes
     */
    private fun updatePlayheadsAsync() = viewModelScope.async {
        currentPlayheads.clear()
        currentPlayheads.putAll(
            Crunchyroll.playheads(episodesCrunchy.data.map { it.id }).toPlayheadsMap()
        )
    }

    /**
     * Set currentSeasonCrunchy based on the season id. Also set the new seasons episodes.
     *
     * @param seasonId the id of the season to set
     */
    suspend fun setCurrentSeason(seasonId: String) {
        // return if the id hasn't changed (performance)
        if (currentSeasonCrunchy.id == seasonId) return

        // set currentSeasonCrunchy to the new season with id == seasonId, if the id isn't found,
        // don't change the current season (this should/can never happen)
        currentSeasonCrunchy = seasonsCrunchy.data.firstOrNull {
            it.id == seasonId
        } ?: currentSeasonCrunchy

        episodesCrunchy = Crunchyroll.episodes(currentSeasonCrunchy.id)
        currentEpisodesCrunchy.clear()
        currentEpisodesCrunchy.addAll(episodesCrunchy.data)

        // update playheads playheads (including fully watched state)
        updatePlayheadsAsync().await()
    }

    suspend fun setWatchlist() {
        isWatchlist = if (isWatchlist) {
            Crunchyroll.deleteWatchlist(seriesCrunchy.id)
            false
        } else {
            Crunchyroll.postWatchlist(seriesCrunchy.id)
            true
        }
    }

    suspend fun updateOnResume() {
        joinAll(
            updatePlayheadsAsync(),
            viewModelScope.launch { upNextSeries = Crunchyroll.upNextSeries(seriesCrunchy.id) }
        )
    }

}
