package com.github.libretube.helpers

import android.annotation.SuppressLint
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.os.Process
import androidx.core.content.getSystemService
import androidx.core.os.bundleOf
import androidx.fragment.app.commitNow
import androidx.fragment.app.replace
import com.github.libretube.NavDirections
import com.github.libretube.R
import com.github.libretube.constants.IntentData
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.enums.FileType
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.toID
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.parcelable.PlayerData
import com.github.libretube.ui.activities.AbstractPlayerHostActivity
import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.activities.ZoomableImageActivity
import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.fragments.AudioPlayerFragment
import com.github.libretube.ui.fragments.DownloadTab
import com.github.libretube.ui.fragments.PlayerFragment
import com.github.libretube.util.PlayingQueue
import com.github.libretube.util.PlayingQueueMode
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.MediaServiceRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import androidx.lifecycle.lifecycleScope
import kotlin.io.path.exists

object NavigationHelper {
    fun navigateChannel(context: Context, channelUrlOrId: String?) {
        if (channelUrlOrId == null) return

        // navigating to channels is only supported in the main activity, not in the no internet activity
        val activity = ContextHelper.tryUnwrapActivity<MainActivity>(context) ?: return
        activity.navController.navigate(NavDirections.openChannel(channelUrlOrId.toID()))
        try {
            // minimize player if currently expanded
            activity.runOnPlayerFragment {
                binding.playerMotionLayout.transitionToEnd()
                true
            }
            activity.minimizePlayerContainerLayout()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * Navigate to the given video using the other provided parameters as well
     * If the audio only mode is enabled, play it in the background, else as a normal video
     */
    @SuppressLint("UnsafeOptInUsageError")
    fun navigateVideo(
        context: Context,
        playerData: PlayerData,
        alreadyStarted: Boolean = false,
        forceVideo: Boolean = false,
        audioOnlyPlayerRequested: Boolean = false,
    ) {
        val resolvedPlayerData = if (
            !playerData.isOffline &&
            playerData.videoId != null &&
            !NetworkHelper.isNetworkAvailable(context)
        ) {
            val localDownload = runBlocking(Dispatchers.IO) {
                DatabaseHolder.Database.downloadDao().findById(playerData.videoId.toID())
            }
            val existingItems = localDownload?.downloadItems.orEmpty().filter { it.path.exists() }
            val hasVideo = existingItems.any { it.type == FileType.VIDEO }
            val hasAudio = existingItems.any { it.type == FileType.AUDIO }
            if (hasVideo || hasAudio) {
                playerData.copy(
                    isOffline = true,
                    downloadTab = if (hasVideo) DownloadTab.VIDEO else DownloadTab.AUDIO
                )
            } else {
                playerData
            }
        } else {
            playerData
        }

        // Only preserve a queue when the caller explicitly asks for it (playlist, play-all, etc.).
        // A manually selected standalone video must become a fresh autoplay/radio seed; otherwise
        // an old vallenato queue could survive after the user deliberately switches to salsa.
        val effectivePlayerData = resolvedPlayerData

        val switchingPlaybackMode = PlayingQueue.isNotEmpty() &&
            ((effectivePlayerData.isOffline && PlayingQueue.queueMode == PlayingQueueMode.ONLINE) ||
                (!effectivePlayerData.isOffline && PlayingQueue.queueMode == PlayingQueueMode.OFFLINE))
        if (switchingPlaybackMode) {
            // Never leave an online and an offline MediaLibraryService alive at the same time.
            // They share PlayingQueue, so overlapping services can race while a downloaded item is
            // being opened and can destabilize the player/fragment transition.
            BackgroundHelper.stopBackgroundPlay(context)
        }

        // attempt to attach to the current media session first by using the corresponding
        // video/audio player instance
        val activity = ContextHelper.unwrapActivity<AbstractPlayerHostActivity>(context)
        val attachedToRunningPlayer = activity.runOnPlayerFragment {
            // can only continue using player if in same mode (online/offline)
            // otherwise, recreate the player
            if (effectivePlayerData.isOffline != isOffline || effectivePlayerData.videoId == null) return@runOnPlayerFragment false

            try {
                val vid = effectivePlayerData.videoId.toID()
                // maximize player immediately
                this.binding.playerMotionLayout.transitionToStart()

                if (isOffline) {
                    // A downloaded item must never wait for an online streams request.
                    this@runOnPlayerFragment.playNextVideo(vid)
                } else {
                    // Online mode can attach cached/prefetched stream metadata for a faster switch.
                    this.lifecycleScope.launch {
                        val streamsJson = runCatching {
                            val streams = MediaServiceRepository.instance.getStreams(vid)
                            JsonHelper.json.encodeToString(streams)
                        }.getOrNull()

                        withContext(Dispatchers.Main) {
                            try {
                                this@runOnPlayerFragment.playNextVideo(vid, streamsJson)
                            } catch (e: Exception) {
                                this@runOnPlayerFragment.playNextVideo(vid)
                            }
                        }
                    }
                }

                if (audioOnlyPlayerRequested) {
                    // switch to audio only player
                    this.switchToAudioMode()
                }

                true
            } catch (e: Exception) {
                this.onDestroy()
                false
            }
        }
        if (attachedToRunningPlayer) return

        val audioOnlyMode = PreferenceHelper.getBoolean(PreferenceKeys.AUDIO_ONLY_MODE, false)
        val attachedToRunningAudioPlayer = activity.runOnAudioPlayerFragment {
            // can only continue using player if in same mode (online/offline)
            // otherwise, recreate the player
            if (effectivePlayerData.isOffline != isOffline || effectivePlayerData.videoId == null) return@runOnAudioPlayerFragment false

            try {
                val vid = effectivePlayerData.videoId.toID()
                this.binding.playerMotionLayout.transitionToStart()
                if (isOffline) {
                    this@runOnAudioPlayerFragment.playNextVideo(vid)
                } else {
                    this.lifecycleScope.launch {
                        val streamsJson = runCatching {
                            val streams = MediaServiceRepository.instance.getStreams(vid)
                            JsonHelper.json.encodeToString(streams)
                        }.getOrNull()

                        withContext(Dispatchers.Main) {
                            try {
                                this@runOnAudioPlayerFragment.playNextVideo(vid, streamsJson)
                            } catch (e: Exception) {
                                this@runOnAudioPlayerFragment.playNextVideo(vid)
                            }
                        }
                    }
                }

                if (!audioOnlyPlayerRequested && !audioOnlyMode) {
                    // switch to video only player
                    this.switchToVideoMode(effectivePlayerData.videoId.toID())
                }

                true
            } catch (e: Exception) {
                false
            }
        }
        if (attachedToRunningAudioPlayer) return

        if (audioOnlyPlayerRequested || (audioOnlyMode && !forceVideo)) {
            // in contrast to the video player, the audio player doesn't start a media service on
            // its own!
            BackgroundHelper.playOnBackground(context, effectivePlayerData)

            openAudioPlayerFragment(context, offlinePlayer = effectivePlayerData.isOffline, minimizeByDefault = true)
        } else {
            openVideoPlayerFragment(
                context,
                effectivePlayerData,
                alreadyStarted
            )
        }
    }

    fun navigatePlaylist(context: Context, playlistUrlOrId: String?, playlistType: PlaylistType) {
        if (playlistUrlOrId == null) return

        val activity = ContextHelper.unwrapActivity<MainActivity>(context)
        activity.navController.navigate(
            NavDirections.openPlaylist(playlistUrlOrId.toID(), playlistType)
        )
    }

    /**
     * Start the audio player fragment
     */
    fun openAudioPlayerFragment(
        context: Context,
        offlinePlayer: Boolean = false,
        minimizeByDefault: Boolean = false
    ) {
        val activity = ContextHelper.unwrapActivity<BaseActivity>(context)
        activity.supportFragmentManager.commitNow {
            val args = bundleOf(
                IntentData.minimizeByDefault to minimizeByDefault,
                IntentData.offlinePlayer to offlinePlayer
            )
            replace<AudioPlayerFragment>(R.id.container, args = args)
        }
    }

    /**
     * Starts the video player fragment for an already existing med
     */
    fun openVideoPlayerFragment(
        context: Context,
        playerData: PlayerData,
        alreadyStarted: Boolean = false,
    ) {
        val activity = ContextHelper.unwrapActivity<BaseActivity>(context)

        val bundle = bundleOf(
            IntentData.playerData to playerData,
            IntentData.alreadyStarted to alreadyStarted,
        )
        activity.supportFragmentManager.commitNow {
            replace<PlayerFragment>(R.id.container, args = bundle)
        }
    }

    /**
     * Open a large, zoomable image preview
     */
    fun openImagePreview(context: Context, url: String) {
        val intent = Intent(context, ZoomableImageActivity::class.java)
        intent.putExtra(IntentData.bitmapUrl, url)
        context.startActivity(intent)
    }

    /**
     * Needed due to different MainActivity Aliases because of the app icons
     */
    fun restartMainActivity(context: Context) {
        // kill player notification
        context.getSystemService<NotificationManager>()!!.cancelAll()
        // start a new Intent of the app
        val pm = context.packageManager
        val intent = pm.getLaunchIntentForPackage(context.packageName)
        intent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
        context.startActivity(intent)
        // kill the old application
        Process.killProcess(Process.myPid())
    }
}
