package audio.funkwhale.ffa.fragments

import android.os.Bundle
import android.util.Log
import android.view.Gravity
import android.view.View
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.liveData
import androidx.lifecycle.map
import androidx.navigation.fragment.findNavController
import audio.funkwhale.ffa.MainNavDirections
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.FragmentNowPlayingBinding
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.repositories.FavoritedRepository
import audio.funkwhale.ffa.repositories.FavoritesRepository
import audio.funkwhale.ffa.repositories.Repository
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.ProgressBus
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.toIntOrElse
import audio.funkwhale.ffa.utils.untilNetwork
import audio.funkwhale.ffa.viewmodel.NowPlayingViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.lang.Float.max

class NowPlayingFragment: Fragment(R.layout.fragment_now_playing) {
  private val binding by lazy { FragmentNowPlayingBinding.bind(requireView()) }
  private val viewModel by viewModels<NowPlayingViewModel>()
  private val favoriteRepository by lazy { FavoritesRepository(requireContext()) }
  private val favoritedRepository by lazy { FavoritedRepository(requireContext()) }

  private var onDetailsMenuItemClickedCb: () -> Unit = {}

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    binding.lifecycleOwner = viewLifecycleOwner

    viewModel.currentTrack.distinctUntilChanged().observe(viewLifecycleOwner, ::onTrackChange)

    with(binding.controls) {
      currentTrackTitle = viewModel.currentTrackTitle
      currentTrackArtist = viewModel.currentTrackArtist
      isCurrentTrackFavorite = viewModel.isCurrentTrackFavorite
      repeatModeResource = viewModel.repeatModeResource
      repeatModeAlpha = viewModel.repeatModeAlpha
      currentProgressText = viewModel.currentProgressText
      currentDurationText = viewModel.currentDurationText
      isPlaying = viewModel.isPlaying
      progress = viewModel.progress

      nowPlayingDetailsPrevious.setOnClickListener {
        CommandBus.send(Command.PreviousTrack)
      }

      nowPlayingDetailsNext.setOnClickListener {
        CommandBus.send(Command.NextTrack)
      }

      nowPlayingDetailsToggle.setOnClickListener {
        CommandBus.send(Command.ToggleState)
      }

      nowPlayingDetailsRepeat.setOnClickListener { toggleRepeatMode() }
      nowPlayingDetailsProgress.setOnSeekBarChangeListener(OnSeekBarChanged())
      nowPlayingDetailsFavorite.setOnClickListener { onFavorite() }
      nowPlayingDetailsAddToPlaylist.setOnClickListener { onAddToPlaylist() }
    }

    binding.nowPlayingDetailsInfo.setOnClickListener { openInfoMenu() }

    with(binding.header) {
      lifecycleOwner = viewLifecycleOwner
      isBuffering = viewModel.isBuffering
      isPlaying = viewModel.isPlaying
      progress = viewModel.progress
      currentTrackTitle = viewModel.currentTrackTitle
      currentTrackArtist = viewModel.currentTrackArtist


      nowPlayingNext.setOnClickListener {
        CommandBus.send(Command.NextTrack)
      }

      nowPlayingToggle.setOnClickListener {
        CommandBus.send(Command.ToggleState)
      }
    }

    lifecycleScope.launch(Dispatchers.Main) {
      CommandBus.get().collect { onCommand(it) }
    }

    lifecycleScope.launch(Dispatchers.Main) {
      ProgressBus.get().collect { onProgress(it) }
    }
  }

  fun onBottomSheetDrag(value: Float) {
    binding.nowPlayingRoot.progress = max(value, 0f)
  }

  fun onDetailsMenuItemClicked(cb: () -> Unit) {
    onDetailsMenuItemClickedCb = cb
  }


  private fun toggleRepeatMode() {
    val cachedRepeatMode = FFACache.getLine(requireContext(), "repeat").toIntOrElse(0)
    val iteratedRepeatMode = (cachedRepeatMode + 1) % 3
    FFACache.set(requireContext(), "repeat", "$iteratedRepeatMode")
    CommandBus.send(Command.SetRepeatMode(iteratedRepeatMode))
  }

  private fun onAddToPlaylist() {
    val currentTrack = viewModel.currentTrack.value ?: return
    CommandBus.send(Command.AddToPlaylist(listOf(currentTrack)))
  }

  private fun onCommand(command: Command) = when (command) {
    is Command.RefreshTrack -> refreshCurrentTrack(command.track)
    is Command.SetRepeatMode -> viewModel.repeatMode.postValue(command.mode)
    else -> {}
  }

  private fun onFavorite() {
    val currentTrack = viewModel.currentTrack.value ?: return

    if (currentTrack.favorite) favoriteRepository.deleteFavorite(currentTrack.id)
    else favoriteRepository.addFavorite(currentTrack.id)

    currentTrack.favorite = !currentTrack.favorite
    // Trigger UI refresh
    viewModel.currentTrack.postValue(viewModel.currentTrack.value)

    favoritedRepository.fetch(Repository.Origin.Network.origin)
  }

  private fun onProgress(state: Triple<Int, Int, Int>) {
    val (current, duration, percent) = state

    val currentMins = (current / 1000) / 60
    val currentSecs = (current / 1000) % 60

    val durationMins = duration / 60
    val durationSecs = duration % 60

    viewModel.progress.postValue(percent)
    viewModel.currentProgressText.postValue("%02d:%02d".format(currentMins, currentSecs))
    viewModel.currentDurationText.postValue("%02d:%02d".format(durationMins, durationSecs))
  }

  private fun onTrackChange(track: Track?) {
    if (track == null) {
      binding.header.nowPlayingCover.setImageResource(R.drawable.cover)
      return
    }

    CoverArt.requestCreator(maybeNormalizeUrl(track.album?.cover()))
      .into(binding.header.nowPlayingCover)
  }

  private fun openInfoMenu() {
    val currentTrack = viewModel.currentTrack.value ?: return

    PopupMenu(
      requireContext(),
      binding.nowPlayingDetailsInfo,
      Gravity.START,
      R.attr.actionOverflowMenuStyle,
      0
    ).apply {
      inflate(R.menu.track_info)

      setOnMenuItemClickListener {
        onDetailsMenuItemClickedCb()

        when (it.itemId) {
          R.id.track_info_artist -> findNavController().navigate(
            MainNavDirections.globalBrowseToAlbums(
              currentTrack.artist,
              currentTrack.album?.cover()
            )
          )
          R.id.track_info_album -> currentTrack.album?.let { album ->
            findNavController().navigate(MainNavDirections.globalBrowseTracks(album))
          }
          R.id.track_info_details -> TrackInfoDetailsFragment.new(currentTrack).show(
            requireActivity().supportFragmentManager, "dialog"
          )
        }

        true
      }

      show()
    }
  }

  private fun refreshCurrentTrack(track: Track?) {
    viewModel.currentTrack.postValue(track)

    val cachedRepeatMode = FFACache.getLine(requireContext(), "repeat").toIntOrElse(0)
    viewModel.repeatMode.postValue(cachedRepeatMode % 3)

    // At this point, a non-null track is required

    if (track == null) return

    favoritedRepository.fetch().untilNetwork(lifecycleScope, Dispatchers.IO) { favorites, _, _, _ ->
      lifecycleScope.launch(Dispatchers.Main) {
        track.favorite = favorites.contains(track.id)
        // Trigger UI refresh
        viewModel.currentTrack.postValue(viewModel.currentTrack.value)
      }
    }
  }

  inner class OnSeekBarChanged : OnSeekBarChangeListener {
    override fun onStopTrackingTouch(view: SeekBar?) {}

    override fun onStartTrackingTouch(view: SeekBar?) {}

    override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) {
      if (fromUser) {
        CommandBus.send(Command.Seek(progress))
      }
    }
  }
}
