package com.github.libretube.repo

import com.github.libretube.api.MediaServiceRepository
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.PlaylistsHelper.MAX_CONCURRENT_IMPORT_CALLS
import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.Playlists
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.db.obj.LocalPlaylist
import com.github.libretube.extensions.parallelMap
import com.github.libretube.obj.PipedImportPlaylist

class LocalPlaylistsRepository: PlaylistRepository {
    private fun toLocalPlaylistId(playlistId: String): Int? = playlistId.toIntOrNull()

    override suspend fun getPlaylist(playlistId: String): Playlist {
        val relation = DatabaseHolder.Database.localPlaylistsDao()
            .getPlaylistWithVideos(toLocalPlaylistId(playlistId)
                ?: throw IllegalArgumentException("Invalid playlist id"))
            ?: throw NoSuchElementException("No local playlist with id $playlistId")

        return Playlist(
            name = relation.playlist.name,
            description = relation.playlist.description,
            thumbnailUrl = relation.playlist.thumbnailUrl,
            videos = relation.videos.size,
            relatedStreams = relation.videos.map { it.toStreamItem() }
        )
    }

    override suspend fun getPlaylists(): List<Playlists> {
        return DatabaseHolder.Database.localPlaylistsDao().getAll()
            .map {
                Playlists(
                    id = it.playlist.id.toString(),
                    name = it.playlist.name,
                    shortDescription = it.playlist.description,
                    thumbnail = it.playlist.thumbnailUrl,
                    videos = it.videos.size.toLong()
                )
            }
    }

    override suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean {
        val playlistIdInt = toLocalPlaylistId(playlistId) ?: return false
        val localPlaylist = DatabaseHolder.Database.localPlaylistsDao()
            .getPlaylistWithVideos(playlistIdInt) ?: return false

        for (video in videos) {
            val localPlaylistItem = video.toLocalPlaylistItem(playlistId)

            val existingVideo = DatabaseHolder.Database.localPlaylistsDao()
                .getPlaylistVideo(playlistIdInt, localPlaylistItem.videoId)
            if (existingVideo != null) {
                localPlaylistItem.id = existingVideo.id
                DatabaseHolder.Database.localPlaylistsDao().updatePlaylistVideo(localPlaylistItem)
                continue
            }

            DatabaseHolder.Database.localPlaylistsDao().addPlaylistVideo(localPlaylistItem)

            val playlist = localPlaylist.playlist
            if (playlist.thumbnailUrl.isEmpty()) {
                localPlaylistItem.thumbnailUrl?.let {
                    playlist.thumbnailUrl = it
                    DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
                }
            }
        }

        return true
    }

    override suspend fun renamePlaylist(playlistId: String, newName: String): Boolean {
        val playlistIdInt = toLocalPlaylistId(playlistId) ?: return false
        val playlist = DatabaseHolder.Database.localPlaylistsDao()
            .getPlaylistWithVideos(playlistIdInt)?.playlist ?: return false
        playlist.name = newName
        DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)

        return true
    }

    override suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean {
        val playlistIdInt = toLocalPlaylistId(playlistId) ?: return false
        val playlist = DatabaseHolder.Database.localPlaylistsDao()
            .getPlaylistWithVideos(playlistIdInt)?.playlist ?: return false
        playlist.description = newDescription
        DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)

        return true
    }

    override suspend fun clonePlaylist(playlistId: String): String {
        val playlist = MediaServiceRepository.instance.getPlaylist(playlistId)
        val newPlaylist = createPlaylist(playlist.name ?: "Unknown name")

        PlaylistsHelper.addToPlaylist(newPlaylist, *playlist.relatedStreams.toTypedArray())

        var nextPage = playlist.nextpage
        while (nextPage != null) {
            nextPage = runCatching {
                MediaServiceRepository.instance.getPlaylistNextPage(playlistId, nextPage!!).apply {
                    PlaylistsHelper.addToPlaylist(newPlaylist, *relatedStreams.toTypedArray())
                }.nextpage
            }.getOrNull()
        }

        return newPlaylist
    }

    override suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean {
        val playlistIdInt = toLocalPlaylistId(playlistId) ?: return false
        val transaction = DatabaseHolder.Database.localPlaylistsDao()
            .getPlaylistWithVideos(playlistIdInt) ?: return false
        DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo(
            transaction.videos[index]
        )
        // set a new playlist thumbnail if the first video got removed
        if (index == 0) {
            transaction.playlist.thumbnailUrl =
                transaction.videos.getOrNull(1)?.thumbnailUrl.orEmpty()
        }
        DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(transaction.playlist)

        return true
    }

    override suspend fun importPlaylists(playlists: List<PipedImportPlaylist>) {
        for (playlist in playlists) {
            val playlistId = createPlaylist(playlist.name!!)

            // if not logged in, all video information needs to become fetched manually
            // Only do so with `MAX_CONCURRENT_IMPORT_CALLS` videos at once to prevent performance issues
            for (videoIdList in playlist.videos.chunked(MAX_CONCURRENT_IMPORT_CALLS)) {
                val streams = videoIdList.parallelMap {
                    runCatching { MediaServiceRepository.instance.getStreams(it) }
                        .getOrNull()
                        ?.toStreamItem(it)
                }.filterNotNull()

                PlaylistsHelper.addToPlaylist(playlistId, *streams.toTypedArray())
            }
        }
    }

    override suspend fun createPlaylist(playlistName: String): String {
        val playlist = LocalPlaylist(name = playlistName, thumbnailUrl = "")
        return DatabaseHolder.Database.localPlaylistsDao().createPlaylist(playlist).toString()
    }

    override suspend fun deletePlaylist(playlistId: String): Boolean {
        val playlistIdInt = toLocalPlaylistId(playlistId) ?: return false
        DatabaseHolder.Database.localPlaylistsDao().deletePlaylistById(playlistIdInt)
        DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByPlaylistId(playlistIdInt)

        return true
    }
}