package org.thoughtcrime.securesms.conversation

import android.annotation.SuppressLint
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.doOnNextLayout
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.FixedRoundedCornerBottomSheetDialogFragment
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager
import org.thoughtcrime.securesms.conversation.colors.ColorizerV1
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ItemDecoration
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackController
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicy
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionPlayerHolder
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionRecycler
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.polls.PollOption
import org.thoughtcrime.securesms.polls.PollRecord
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.NetworkUtil
import org.thoughtcrime.securesms.util.StickyHeaderDecoration
import org.thoughtcrime.securesms.util.fragments.findListener
import org.thoughtcrime.securesms.util.visible
import java.util.Locale

/**
 * Bottom sheet to show all pinned messages
 */
class PinnedMessagesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment() {

  override val peekHeightPercentage: Float = 1f
  override val themeResId: Int = R.style.Widget_Signal_FixedRoundedCorners_Messages

  private lateinit var messageAdapter: ConversationAdapter
  private val viewModel: PinnedMessagesViewModel by viewModels(
    factoryProducer = {
      val threadId = requireArguments().getLong(KEY_THREAD_ID, -1)
      val conversationRecipientId = RecipientId.from(arguments?.getString(KEY_CONVERSATION_RECIPIENT_ID, null) ?: throw IllegalArgumentException())

      PinnedMessagesViewModel.Factory(AppDependencies.application, threadId, conversationRecipientId)
    }
  )

  private val disposables: LifecycleDisposable = LifecycleDisposable()

  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
    val view = inflater.inflate(R.layout.pinned_messages_bottom_sheet, container, false)
    disposables.bindTo(viewLifecycleOwner)
    return view
  }

  @SuppressLint("WrongThread")
  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val conversationRecipientId = RecipientId.from(arguments?.getString(KEY_CONVERSATION_RECIPIENT_ID, null) ?: throw IllegalArgumentException())
    val conversationRecipient = Recipient.resolved(conversationRecipientId)

    @Suppress("DEPRECATION")
    val colorizer = ColorizerV1()

    messageAdapter = ConversationAdapter(requireContext(), viewLifecycleOwner, Glide.with(this), Locale.getDefault(), ConversationAdapterListener(), conversationRecipient.hasWallpaper, colorizer).apply {
      setCondensedMode(ConversationItemDisplayMode.Condensed(ConversationItemDisplayMode.MessageMode.PINNED))
    }

    val list: RecyclerView = view.findViewById<RecyclerView>(R.id.pinned_list).apply {
      layoutManager = SmoothScrollingLinearLayoutManager(requireContext(), true)
      adapter = messageAdapter

      doOnNextLayout {
        // Adding this without waiting for a layout pass would result in an indeterminate amount of padding added to the top of the view
        addItemDecoration(StickyHeaderDecoration(messageAdapter, false, false, ConversationAdapter.HEADER_TYPE_INLINE_DATE))
      }
    }

    val recyclerViewColorizer = RecyclerViewColorizer(list)

    disposables += viewModel.getMessages().subscribe { messages ->
      if (messages.isEmpty()) {
        dismiss()
      }

      val currentMessages = messageAdapter.currentList
      if (currentMessages != messages) {
        messageAdapter.submitList(messages) {
          if (!list.canScrollVertically(1)) {
            list.layoutManager?.scrollToPosition(0)
          }
        }
      }
      recyclerViewColorizer.setChatColors(conversationRecipient.chatColors)
    }

    disposables += viewModel.getNameColorsMap().subscribe { map ->
      colorizer.onNameColorsChanged(map)
      messageAdapter.notifyItemRangeChanged(0, messageAdapter.itemCount, ConversationAdapterBridge.PAYLOAD_NAME_COLORS)
    }

    initializeGiphyMp4(view.findViewById(R.id.video_container)!!, list)

    val unpinAll = view.findViewById<TextView>(R.id.unpin_all)
    unpinAll.setOnClickListener {
      MaterialAlertDialogBuilder(requireContext())
        .setTitle(R.string.PinnedMessage__unpin_title)
        .setMessage(getString(R.string.PinnedMessage__unpin_body))
        .setPositiveButton(R.string.PinnedMessage__unpin) { dialog, which ->
          if (NetworkUtil.isConnected(requireContext())) {
            viewModel.unpinMessage()
            dismissAllowingStateLoss()
          } else {
            showNetworkErrorDialog()
          }
        }
        .setNegativeButton(android.R.string.cancel) { dialog, which -> dialog.dismiss() }
        .show()
    }
    unpinAll.visible = requireArguments().getBoolean(KEY_CAN_UNPIN)
  }

  private fun showNetworkErrorDialog() {
    MaterialAlertDialogBuilder(requireContext())
      .setTitle(R.string.PinnedMessage__couldnt_unpin_all)
      .setMessage(getString(R.string.PinnedMessage__check_connection))
      .setPositiveButton(android.R.string.ok, null)
      .show()
  }

  private fun initializeGiphyMp4(videoContainer: ViewGroup, list: RecyclerView): GiphyMp4ProjectionRecycler {
    val maxPlayback = GiphyMp4PlaybackPolicy.maxSimultaneousPlaybackInConversation()
    val holders = GiphyMp4ProjectionPlayerHolder.injectVideoViews(
      requireContext(),
      viewLifecycleOwner.lifecycle,
      videoContainer,
      maxPlayback
    )
    val callback = GiphyMp4ProjectionRecycler(holders)

    GiphyMp4PlaybackController.attach(list, callback, maxPlayback)
    list.addItemDecoration(GiphyMp4ItemDecoration(callback), 0)

    return callback
  }

  private fun getCallback(): ConversationBottomSheetCallback {
    return findListener<ConversationBottomSheetCallback>() ?: throw IllegalStateException("Parent must implement callback interface!")
  }

  private fun getAdapterListener(): ConversationAdapter.ItemClickListener {
    return getCallback().getConversationAdapterListener()
  }

  private inner class ConversationAdapterListener : ConversationAdapter.ItemClickListener by getAdapterListener() {
    override fun onItemClick(item: MultiselectPart) {
      dismiss()
      getCallback().jumpToMessage(item.getMessageRecord())
    }

    override fun onItemLongClick(itemView: View, item: MultiselectPart) {
      PinnedContextMenu.show(
        context = itemView.context,
        anchorView = itemView,
        rootView = itemView.rootView as ViewGroup,
        message = item.conversationMessage.messageRecord as MmsMessageRecord,
        isGroup = item.conversationMessage.threadRecipient.isPushV2Group,
        canUnpin = requireArguments().getBoolean(KEY_CAN_UNPIN),
        onUnpin = {
          dismiss()
          getCallback().unpin(item.conversationMessage)
        },
        onCopy = {
          getCallback().copy(item.conversationMessage)
        },
        onDelete = {
          dismiss()
          getCallback().delete(item.conversationMessage)
        },
        onSave = {
          getCallback().save(item.conversationMessage)
        }
      )
    }

    override fun onQuoteClicked(messageRecord: MmsMessageRecord) {
      dismiss()
      getCallback().jumpToMessage(messageRecord)
    }

    override fun onLinkPreviewClicked(linkPreview: LinkPreview) {
      dismiss()
      getAdapterListener().onLinkPreviewClicked(linkPreview)
    }

    override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) {
      dismiss()
      getAdapterListener().onQuotedIndicatorClicked(messageRecord)
    }

    override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) {
      dismiss()
      getCallback().jumpToMessage(multiselectPart.conversationMessage.messageRecord)
    }

    override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) {
      dismiss()
      getAdapterListener().onGroupMemberClicked(recipientId, groupId)
    }

    override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) {
      dismiss()
      getAdapterListener().onMessageWithRecaptchaNeededClicked(messageRecord)
    }

    override fun onSingleVoiceNotePlay(uri: Uri, messageId: Long, position: Double) {
      super.onSingleVoiceNotePlay(uri, messageId, position)
    }

    override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) {
      dismiss()
      getAdapterListener().onGroupMigrationLearnMoreClicked(membershipChange)
    }

    override fun onChatSessionRefreshLearnMoreClicked() {
      dismiss()
      getAdapterListener().onChatSessionRefreshLearnMoreClicked()
    }

    override fun onBadDecryptLearnMoreClicked(author: RecipientId) {
      dismiss()
      getAdapterListener().onBadDecryptLearnMoreClicked(author)
    }

    override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) {
      dismiss()
      getAdapterListener().onSafetyNumberLearnMoreClicked(recipient)
    }

    override fun onJoinGroupCallClicked() {
      dismiss()
      getAdapterListener().onJoinGroupCallClicked()
    }

    override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) {
      dismiss()
      getAdapterListener().onInviteFriendsToGroupClicked(groupId)
    }

    override fun onEnableCallNotificationsClicked() {
      dismiss()
      getAdapterListener().onEnableCallNotificationsClicked()
    }

    override fun onCallToAction(action: String) {
      dismiss()
      getAdapterListener().onCallToAction(action)
    }

    override fun onDonateClicked() {
      dismiss()
      getAdapterListener().onDonateClicked()
    }

    override fun onRecipientNameClicked(target: RecipientId) {
      dismiss()
      getAdapterListener().onRecipientNameClicked(target)
    }

    override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) {
      dismiss()
      getAdapterListener().onViewGiftBadgeClicked(messageRecord)
    }

    override fun onActivatePaymentsClicked() {
      dismiss()
      getAdapterListener().onActivatePaymentsClicked()
    }

    override fun onSendPaymentClicked(recipientId: RecipientId) {
      dismiss()
      getAdapterListener().onSendPaymentClicked(recipientId)
    }

    override fun onEditedIndicatorClicked(conversationMessage: ConversationMessage) {
      dismiss()
      getAdapterListener().onEditedIndicatorClicked(conversationMessage)
    }

    override fun onShowSafetyTips(forGroup: Boolean) = Unit
    override fun onReportSpamLearnMoreClicked() = Unit
    override fun onMessageRequestAcceptOptionsClicked() = Unit
    override fun onItemDoubleClick(item: MultiselectPart) = Unit
    override fun onToggleVote(poll: PollRecord, pollOption: PollOption, isChecked: Boolean?) = Unit
    override fun onViewResultsClicked(pollId: Long) = Unit
  }

  companion object {
    private val TAG = Log.tag(PinnedMessagesBottomSheet::class.java)

    private const val KEY_THREAD_ID = "thread_id"
    private const val KEY_CONVERSATION_RECIPIENT_ID = "conversation_recipient_id"
    private const val KEY_CAN_UNPIN = "can_unpin"

    @JvmStatic
    fun show(fragmentManager: FragmentManager, threadId: Long, conversationRecipientId: RecipientId, canUnpin: Boolean) {
      val args = Bundle().apply {
        putLong(KEY_THREAD_ID, threadId)
        putString(KEY_CONVERSATION_RECIPIENT_ID, conversationRecipientId.serialize())
        putBoolean(KEY_CAN_UNPIN, canUnpin)
      }

      val fragment = PinnedMessagesBottomSheet().apply {
        arguments = args
      }

      fragment.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
    }
  }
}
