package org.thoughtcrime.securesms.mediasend.v2

import android.animation.ValueAnimator
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.KeyEvent
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatDelegate
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.google.android.material.animation.ArgbEvaluatorCompat
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.signal.core.util.BreakIteratorCompat
import org.signal.core.util.OVERRIDE_TRANSITION_CLOSE_COMPAT
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.getParcelableArrayListExtraCompat
import org.signal.core.util.getParcelableExtraCompat
import org.signal.core.util.logging.Log
import org.signal.core.util.overrideActivityTransitionCompat
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil
import org.thoughtcrime.securesms.mediasend.CameraDisplay
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
import org.thoughtcrime.securesms.mediasend.v2.review.MediaReviewFragment
import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationViewModel
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendRepository
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
import org.thoughtcrime.securesms.stories.Stories
import org.thoughtcrime.securesms.util.Debouncer
import org.thoughtcrime.securesms.util.FullscreenHelper
import org.thoughtcrime.securesms.util.WindowUtil
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.util.visible

class MediaSelectionActivity :
  PassphraseRequiredActivity(),
  MediaReviewFragment.Callback,
  EmojiKeyboardPageFragment.Callback,
  EmojiEventListener,
  EmojiSearchFragment.Callback {

  private var animateInShadowLayerValueAnimator: ValueAnimator? = null
  private var animateInTextColorValueAnimator: ValueAnimator? = null
  private var animateOutShadowLayerValueAnimator: ValueAnimator? = null
  private var animateOutTextColorValueAnimator: ValueAnimator? = null

  lateinit var viewModel: MediaSelectionViewModel

  private val lifecycleDisposable = LifecycleDisposable()

  private val textViewModel: TextStoryPostCreationViewModel by viewModels(
    factoryProducer = {
      TextStoryPostCreationViewModel.Factory(TextStoryPostSendRepository())
    }
  )

  private val destination: MediaSelectionDestination
    get() = MediaSelectionDestination.fromBundle(requireNotNull(intent.getBundleExtra(DESTINATION)))

  private val isStory: Boolean
    get() = intent.getBooleanExtra(IS_STORY, false)

  private val shareToTextStory: Boolean
    get() = intent.getBooleanExtra(AS_TEXT_STORY, false)

  private val draftText: CharSequence?
    get() = intent.getCharSequenceExtra(MESSAGE)

  private val debouncer = Debouncer(200)

  override fun attachBaseContext(newBase: Context) {
    delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES
    super.attachBaseContext(newBase)
  }

  override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
    setContentView(R.layout.media_selection_activity)

    FullscreenHelper.showSystemUI(window)
    WindowUtil.setNavigationBarColor(this, 0x01000000)
    WindowUtil.setStatusBarColor(window, Color.TRANSPARENT)

    val sendType: MessageSendType = requireNotNull(intent.getParcelableExtraCompat(MESSAGE_SEND_TYPE, MessageSendType::class.java))
    val initialMedia: List<Media> = intent.getParcelableArrayListExtraCompat(MEDIA, Media::class.java) ?: listOf()
    val message: CharSequence? = if (shareToTextStory) null else draftText
    val isReply: Boolean = intent.getBooleanExtra(IS_REPLY, false)
    val isAddToGroupStoryFlow: Boolean = intent.getBooleanExtra(IS_ADD_TO_GROUP_STORY_FLOW, false)

    val factory = MediaSelectionViewModel.Factory(destination, sendType, initialMedia, message, isReply, isStory, isAddToGroupStoryFlow, MediaSelectionRepository(this))
    viewModel = ViewModelProvider(this, factory)[MediaSelectionViewModel::class.java]

    val textStoryToggle: ConstraintLayout = findViewById(R.id.switch_widget)
    val cameraDisplay = CameraDisplay.getDisplay(this)

    textStoryToggle.updateLayoutParams<FrameLayout.LayoutParams> {
      bottomMargin = cameraDisplay.getToggleBottomMargin()
    }

    val cameraSelectedConstraintSet = ConstraintSet().apply {
      clone(textStoryToggle)
    }
    val textSelectedConstraintSet = ConstraintSet().apply {
      clone(this@MediaSelectionActivity, R.layout.media_selection_activity_text_selected_constraints)
    }

    val textSwitch: TextView = findViewById(R.id.text_switch)
    val cameraSwitch: TextView = findViewById(R.id.camera_switch)

    textSwitch.setOnClickListener {
      viewModel.sendCommand(HudCommand.GoToText)
    }

    cameraSwitch.setOnClickListener {
      debouncer.publish { viewModel.sendCommand(HudCommand.GoToCapture) }
    }

    if (savedInstanceState == null) {
      if (shareToTextStory) {
        initializeTextStory()
      }

      cameraSwitch.isSelected = true

      val navHostFragment = NavHostFragment.create(R.navigation.media)

      supportFragmentManager
        .beginTransaction()
        .replace(R.id.fragment_container, navHostFragment, NAV_HOST_TAG)
        .commitNowAllowingStateLoss()

      navigateToStartDestination()
    } else {
      viewModel.onRestoreState(this, savedInstanceState)
      textViewModel.restoreFromInstanceState(savedInstanceState)
    }

    (supportFragmentManager.findFragmentByTag(NAV_HOST_TAG) as NavHostFragment).navController.addOnDestinationChangedListener { _, d, _ ->
      when (d.id) {
        R.id.mediaCaptureFragment -> {
          textStoryToggle.visible = canDisplayStorySwitch()

          animateTextStyling(cameraSwitch, textSwitch, 200)
          TransitionManager.beginDelayedTransition(textStoryToggle, AutoTransition().setDuration(200))
          cameraSelectedConstraintSet.applyTo(textStoryToggle)
        }

        R.id.textStoryPostCreationFragment -> {
          textStoryToggle.visible = canDisplayStorySwitch()

          animateTextStyling(textSwitch, cameraSwitch, 200)
          TransitionManager.beginDelayedTransition(textStoryToggle, AutoTransition().setDuration(200))
          textSelectedConstraintSet.applyTo(textStoryToggle)
        }

        else -> textStoryToggle.visible = false
      }
    }

    lifecycleDisposable.bindTo(this)
    lifecycleDisposable += viewModel.mediaErrors
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe(this::handleError)

    onBackPressedDispatcher.addCallback(OnBackPressed())
  }

  private fun handleError(error: MediaValidator.FilterError) {
    when (error) {
      MediaValidator.FilterError.None -> return
      MediaValidator.FilterError.ItemTooLarge -> Toast.makeText(this, R.string.MediaReviewFragment__one_or_more_items_were_too_large, Toast.LENGTH_SHORT).show()
      MediaValidator.FilterError.ItemInvalidType -> Toast.makeText(this, R.string.MediaReviewFragment__one_or_more_items_were_invalid, Toast.LENGTH_SHORT).show()
      MediaValidator.FilterError.TooManyItems -> Toast.makeText(this, R.string.MediaReviewFragment__too_many_items_selected, Toast.LENGTH_SHORT).show()
      is MediaValidator.FilterError.NoItems -> {
        if (error.cause != null) {
          handleError(error.cause)
        }
      }
    }

    viewModel.clearMediaErrors()
  }

  private fun animateTextStyling(selectedSwitch: TextView, unselectedSwitch: TextView, duration: Long) {
    val offTextColor = ContextCompat.getColor(this, R.color.signal_colorOnSurface)
    val onTextColor = ContextCompat.getColor(this, R.color.signal_colorSecondaryContainer)

    animateInShadowLayerValueAnimator?.cancel()
    animateInTextColorValueAnimator?.cancel()
    animateOutShadowLayerValueAnimator?.cancel()
    animateOutTextColorValueAnimator?.cancel()

    animateInShadowLayerValueAnimator = ValueAnimator.ofFloat(selectedSwitch.shadowRadius, 0f).apply {
      this.duration = duration
      addUpdateListener { selectedSwitch.setShadowLayer(it.animatedValue as Float, 0f, 0f, Color.BLACK) }
      start()
    }
    animateInTextColorValueAnimator = ValueAnimator.ofObject(ArgbEvaluatorCompat(), selectedSwitch.currentTextColor, onTextColor).apply {
      setEvaluator(ArgbEvaluatorCompat.getInstance())
      this.duration = duration
      addUpdateListener { selectedSwitch.setTextColor(it.animatedValue as Int) }
      start()
    }
    animateOutShadowLayerValueAnimator = ValueAnimator.ofFloat(unselectedSwitch.shadowRadius, 3f).apply {
      this.duration = duration
      addUpdateListener { unselectedSwitch.setShadowLayer(it.animatedValue as Float, 0f, 0f, Color.BLACK) }
      start()
    }
    animateOutTextColorValueAnimator = ValueAnimator.ofObject(ArgbEvaluatorCompat(), unselectedSwitch.currentTextColor, offTextColor).apply {
      setEvaluator(ArgbEvaluatorCompat.getInstance())
      this.duration = duration
      addUpdateListener { unselectedSwitch.setTextColor(it.animatedValue as Int) }
      start()
    }
  }

  private fun initializeTextStory() {
    val message = draftText?.toString() ?: return
    val firstLink = LinkPreviewUtil.findValidPreviewUrls(message).findFirst()
    val firstLinkUrl = firstLink.map { it.url }.orElse(null)

    val iterator = BreakIteratorCompat.getInstance()
    iterator.setText(message)
    val trimmedMessage = iterator.take(700).toString()

    if (firstLinkUrl == message) {
      textViewModel.setLinkPreview(firstLinkUrl)
    } else if (firstLinkUrl != null) {
      textViewModel.setLinkPreview(firstLinkUrl)
      textViewModel.setBody(trimmedMessage.replace(firstLinkUrl, "").trim())
    } else {
      textViewModel.setBody(trimmedMessage.trim())
    }
  }

  private fun canDisplayStorySwitch(): Boolean {
    return Stories.isFeatureEnabled() &&
      isCameraFirst() &&
      !viewModel.hasSelectedMedia() &&
      (destination == MediaSelectionDestination.ChooseAfterMediaSelection || destination is MediaSelectionDestination.SingleStory)
  }

  override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    viewModel.onSaveState(outState)
    textViewModel.saveToInstanceState(outState)
  }

  override fun onSentWithResult(mediaSendActivityResult: MediaSendActivityResult) {
    setResult(
      RESULT_OK,
      Intent().apply {
        putExtra(MediaSendActivityResult.EXTRA_RESULT, mediaSendActivityResult)
      }
    )

    finish()
    overrideActivityTransitionCompat(OVERRIDE_TRANSITION_CLOSE_COMPAT, R.anim.stationary, R.anim.camera_slide_to_bottom)
  }

  override fun onSentWithoutResult() {
    val intent = Intent()
    setResult(RESULT_OK, intent)

    finish()
    overrideActivityTransitionCompat(OVERRIDE_TRANSITION_CLOSE_COMPAT, R.anim.stationary, R.anim.camera_slide_to_bottom)
  }

  override fun onSendError(error: Throwable) {
    if (error is UntrustedRecords.UntrustedRecordsException) {
      Log.w(TAG, "Send failed due to untrusted identities.")
      SafetyNumberBottomSheet
        .forIdentityRecordsAndDestinations(error.untrustedRecords, error.destinations.toList())
        .show(supportFragmentManager)
    } else {
      setResult(RESULT_CANCELED)

      // TODO [alex] - Toast
      Log.w(TAG, "Failed to send message.", error)

      finish()
      overrideActivityTransitionCompat(OVERRIDE_TRANSITION_CLOSE_COMPAT, R.anim.stationary, R.anim.camera_slide_to_bottom)
    }
  }

  override fun onNoMediaSelected() {
    Log.w(TAG, "No media selected. Exiting.")

    setResult(RESULT_CANCELED)
    finish()
    overrideActivityTransitionCompat(OVERRIDE_TRANSITION_CLOSE_COMPAT, R.anim.stationary, R.anim.camera_slide_to_bottom)
  }

  override fun onPopFromReview() {
    if (isCameraFirst()) {
      viewModel.removeCameraFirstCapture()
    }

    if (!navigateToStartDestination()) {
      finish()
    }
  }

  private fun navigateToStartDestination(navHostFragment: NavHostFragment? = null): Boolean {
    val hostFragment: NavHostFragment = navHostFragment ?: supportFragmentManager.findFragmentByTag(NAV_HOST_TAG) as NavHostFragment

    val startDestination: Int = intent.getIntExtra(START_ACTION, -1)
    return if (startDestination > 0) {
      hostFragment.navController.safeNavigate(
        startDestination,
        Bundle().apply {
          putBoolean("first", true)
        }
      )

      true
    } else {
      false
    }
  }

  private fun isCameraFirst(): Boolean = intent.getIntExtra(START_ACTION, -1) == R.id.action_directly_to_mediaCaptureFragment

  override fun openEmojiSearch() {
    viewModel.sendCommand(HudCommand.OpenEmojiSearch)
  }

  override fun onEmojiSelected(emoji: String?) {
    viewModel.sendCommand(HudCommand.EmojiInsert(emoji))
  }

  override fun onKeyEvent(keyEvent: KeyEvent?) {
    viewModel.sendCommand(HudCommand.EmojiKeyEvent(keyEvent))
  }

  override fun closeEmojiSearch() {
    viewModel.sendCommand(HudCommand.CloseEmojiSearch)
  }

  private inner class OnBackPressed : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
      val navController = this@MediaSelectionActivity.findNavController(R.id.fragment_container)

      if (shareToTextStory && navController.currentDestination?.id == R.id.textStoryPostCreationFragment) {
        finish()
      }

      if (!navController.popBackStack()) {
        finish()
      }
    }
  }

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

    private const val NAV_HOST_TAG = "NAV_HOST"

    private const val START_ACTION = "start.action"
    private const val MESSAGE_SEND_TYPE = "message.send.type"
    private const val MEDIA = "media"
    private const val MESSAGE = "message"
    private const val DESTINATION = "destination"
    private const val IS_REPLY = "is_reply"
    private const val IS_STORY = "is_story"
    private const val AS_TEXT_STORY = "as_text_story"
    private const val IS_ADD_TO_GROUP_STORY_FLOW = "is_add_to_group_story_flow"

    @JvmStatic
    fun camera(context: Context): Intent {
      return camera(context, false)
    }

    @JvmStatic
    fun camera(context: Context, isStory: Boolean): Intent {
      return buildIntent(
        context = context,
        startAction = R.id.action_directly_to_mediaCaptureFragment,
        isStory = isStory
      )
    }

    fun addToGroupStory(
      context: Context,
      recipientId: RecipientId
    ): Intent {
      return buildIntent(
        context = context,
        startAction = R.id.action_directly_to_mediaCaptureFragment,
        isStory = true,
        isAddToGroupStoryFlow = true,
        destination = MediaSelectionDestination.SingleStory(recipientId)
      )
    }

    @JvmStatic
    fun camera(
      context: Context,
      messageSendType: MessageSendType,
      recipientId: RecipientId,
      isReply: Boolean
    ): Intent {
      return buildIntent(
        context = context,
        startAction = R.id.action_directly_to_mediaCaptureFragment,
        messageSendType = messageSendType,
        destination = MediaSelectionDestination.SingleRecipient(recipientId),
        isReply = isReply
      )
    }

    @JvmStatic
    fun gallery(
      context: Context,
      messageSendType: MessageSendType,
      media: List<Media>,
      recipientId: RecipientId,
      message: CharSequence?,
      isReply: Boolean
    ): Intent {
      return buildIntent(
        context = context,
        startAction = R.id.action_directly_to_mediaGalleryFragment,
        messageSendType = messageSendType,
        media = media,
        destination = MediaSelectionDestination.SingleRecipient(recipientId),
        message = message,
        isReply = isReply
      )
    }

    @JvmStatic
    fun editor(
      context: Context,
      messageSendType: MessageSendType,
      media: List<Media>,
      recipientId: RecipientId,
      message: CharSequence?
    ): Intent {
      return buildIntent(
        context = context,
        messageSendType = messageSendType,
        media = media,
        destination = MediaSelectionDestination.SingleRecipient(recipientId),
        message = message
      )
    }

    @JvmStatic
    fun editor(
      context: Context,
      media: List<Media>
    ): Intent {
      return buildIntent(
        context = context,
        media = media
      )
    }

    @JvmStatic
    fun share(
      context: Context,
      messageSendType: MessageSendType,
      media: List<Media>,
      recipientSearchKeys: List<ContactSearchKey.RecipientSearchKey>,
      message: CharSequence?,
      asTextStory: Boolean
    ): Intent {
      return buildIntent(
        context = context,
        messageSendType = messageSendType,
        media = media,
        destination = MediaSelectionDestination.MultipleRecipients(recipientSearchKeys),
        message = message,
        asTextStory = asTextStory,
        startAction = if (asTextStory) R.id.action_directly_to_textPostCreationFragment else -1,
        isStory = recipientSearchKeys.any { it.isStory }
      )
    }

    private fun buildIntent(
      context: Context,
      startAction: Int = -1,
      messageSendType: MessageSendType = MessageSendType.SignalMessageSendType,
      media: List<Media> = listOf(),
      destination: MediaSelectionDestination = MediaSelectionDestination.ChooseAfterMediaSelection,
      message: CharSequence? = null,
      isReply: Boolean = false,
      isStory: Boolean = false,
      asTextStory: Boolean = false,
      isAddToGroupStoryFlow: Boolean = false
    ): Intent {
      return Intent(context, MediaSelectionActivity::class.java).apply {
        putExtra(START_ACTION, startAction)
        putExtra(MESSAGE_SEND_TYPE, messageSendType)
        putParcelableArrayListExtra(MEDIA, ArrayList(media))
        putExtra(MESSAGE, message)
        putExtra(DESTINATION, destination.toBundle())
        putExtra(IS_REPLY, isReply)
        putExtra(IS_STORY, isStory)
        putExtra(AS_TEXT_STORY, asTextStory)
        putExtra(IS_ADD_TO_GROUP_STORY_FLOW, isAddToGroupStoryFlow)
      }
    }
  }
}
