package org.thoughtcrime.securesms.keyvalue

import org.signal.archive.proto.BackupDebugInfo
import org.signal.ringrtc.CallManager.DataMode
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.database.model.IssuePriority
import org.thoughtcrime.securesms.util.Environment.Calling.defaultSfuUrl
import org.thoughtcrime.securesms.util.RemoteConfig

class InternalValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
  companion object {
    const val GV2_FORCE_INVITES: String = "internal.gv2.force_invites"
    const val GV2_IGNORE_P2P_CHANGES: String = "internal.gv2.ignore_p2p_changes"
    const val RECIPIENT_DETAILS: String = "internal.recipient_details"
    const val ALLOW_CENSORSHIP_SETTING: String = "internal.force_censorship"
    const val FORCE_BUILT_IN_EMOJI: String = "internal.force_built_in_emoji"
    const val REMOVE_SENDER_KEY_MINIMUM: String = "internal.remove_sender_key_minimum"
    const val DELAY_RESENDS: String = "internal.delay_resends"
    const val CALLING_SERVER: String = "internal.calling_server"
    const val CALLING_DATA_MODE: String = "internal.calling_bandwidth_mode"
    const val CALLING_DISABLE_TELECOM: String = "internal.calling_disable_telecom"
    const val CALLING_SET_AUDIO_CONFIG: String = "internal.calling_set_audio_config"
    const val CALLING_USE_OBOE_ADM: String = "internal.calling_use_oboe_adm"
    const val CALLING_USE_SOFTWARE_AEC: String = "internal.calling_use_software_aec"
    const val CALLING_USE_SOFTWARE_NS: String = "internal.calling_use_software_ns"
    const val CALLING_USE_INPUT_LOW_LATENCY: String = "internal.calling_use_input_low_latency"
    const val CALLING_USE_INPUT_VOICE_COMM: String = "internal.calling_use_input_voice_comm"
    const val CALLING_SET_VIDEO_CONFIG: String = "internal.calling_set_video_config"
    const val CALLING_USE_HARDWARE_VP9_ENCODE: String = "internal.calling_use_hardware_vp9_encode"
    const val CALLING_USE_HARDWARE_VP9_DECODE: String = "internal.calling_use_hardware_vp9_decode"
    const val CALLING_USE_SOFTWARE_VP9_ENCODE: String = "internal.calling_use_software_vp9_encode"
    const val CALLING_USE_SOFTWARE_VP9_DECODE: String = "internal.calling_use_software_vp9_decode"
    const val SHAKE_TO_REPORT: String = "internal.shake_to_report"
    const val DISABLE_STORAGE_SERVICE: String = "internal.disable_storage_service"
    const val LAST_SCROLL_POSITION: String = "internal.last_scroll_position"
    const val CONVERSATION_ITEM_V2_MEDIA: String = "internal.conversation_item_v2_media"
    const val WEB_SOCKET_SHADOWING_STATS: String = "internal.web_socket_shadowing_stats"
    const val FORCE_SPLIT_PANE_ON_COMPACT_LANDSCAPE: String = "internal.force.split.pane.on.compact.landscape.ui"
    const val FORCE_SINGLE_PANE_ON_ALL_DEVICES: String = "internal.force_single_pane_on_all_devices"
    const val SHOW_ARCHIVE_STATE_HINT: String = "internal.show_archive_state_hint"
    const val INCLUDE_DEBUGLOG_IN_BACKUP: String = "internal.include_debuglog_in_backup"
    const val IMPORTED_BACKUP_DEBUG_INFO: String = "internal.imported_backup_debug_info"
    const val USE_NEW_MEDIA_ACTIVITY: String = "internal.use_new_media_activity"
    const val ANR_DETECTION_CRASH: String = "internal.anr_detection_crash"
    const val ISSUE_NOTIFICATION_PRIORITY: String = "internal.issue_notification_priority"
  }

  public override fun onFirstEverAppLaunch() = Unit

  public override fun getKeysToIncludeInBackup(): List<String> = emptyList()

  /**
   * Force split-pane mode on compact landscape
   */
  var forceSplitPane by booleanValue(FORCE_SPLIT_PANE_ON_COMPACT_LANDSCAPE, false).falseForExternalUsers()

  /**
   * Force single-pane on all devices
   */
  var forceSinglePane by booleanValue(FORCE_SINGLE_PANE_ON_ALL_DEVICES, false).falseForExternalUsers()

  /**
   * Whether to use the new media-send flow. Internal users can override the remote value.
   */
  var useNewMediaActivity: Boolean
    get() = if (RemoteConfig.internalUser) {
      getBoolean(USE_NEW_MEDIA_ACTIVITY, RemoteConfig.useNewMediaSendFlow)
    } else {
      RemoteConfig.useNewMediaSendFlow
    }
    set(value) {
      putBoolean(USE_NEW_MEDIA_ACTIVITY, value)
    }

  /**
   * Members will not be added directly to a GV2 even if they could be.
   */
  var gv2ForceInvites by booleanValue(GV2_FORCE_INVITES, false).defaultForExternalUsers()

  /**
   * Signed group changes are sent P2P, if the client ignores them, it will then ask the server
   * directly which allows testing of certain testing scenarios.
   */
  var gv2IgnoreP2PChanges by booleanValue(GV2_IGNORE_P2P_CHANGES, false).defaultForExternalUsers()

  /**
   * Show detailed recipient info in the [org.thoughtcrime.securesms.components.settings.conversation.InternalConversationSettingsFragment].
   */
  var recipientDetails by booleanValue(RECIPIENT_DETAILS, true).falseForExternalUsers()

  /**
   * Allow changing the censorship circumvention setting regardless of network status.
   */
  var allowChangingCensorshipSetting by booleanValue(ALLOW_CENSORSHIP_SETTING, false).defaultForExternalUsers()

  /**
   * Force the app to use the emoji that ship with the app, as opposed to the ones that were downloaded.
   */
  var forceBuiltInEmoji by booleanValue(FORCE_BUILT_IN_EMOJI, false).defaultForExternalUsers()

  /**
   * Remove the requirement that there must be two sender-key-capable recipients to use sender key
   */
  var removeSenderKeyMinimum by booleanValue(REMOVE_SENDER_KEY_MINIMUM, false).defaultForExternalUsers()

  /**
   * Delay resending messages in response to retry receipts by 10 seconds.
   */
  var delayResends by booleanValue(DELAY_RESENDS, false).defaultForExternalUsers()

  /**
   * Whether or not "shake to report" is enabled.
   */
  var shakeToReport: Boolean by booleanValue(SHAKE_TO_REPORT, true).falseForExternalUsers()

  /**
   * Whether or not storage service is manually disabled.
   */
  var storageServiceDisabled by booleanValue(DISABLE_STORAGE_SERVICE, false).defaultForExternalUsers()

  /**
   * The selected group calling server to use.
   *
   *
   * The user must be an internal user and the setting must be one of the current set of internal servers otherwise
   * the default SFU will be returned. This ensures that if the [BuildConfig.SIGNAL_SFU_INTERNAL_URLS] list changes,
   * internal users cannot be left on old servers.
   */
  var groupCallingServer: String
    get() {
      var internalServer = if (RemoteConfig.internalUser) getString(CALLING_SERVER, defaultSfuUrl()) else null
      if (internalServer != null && !listOf(*BuildConfig.SIGNAL_SFU_INTERNAL_URLS).contains(internalServer)) {
        internalServer = null
      }
      return internalServer ?: BuildConfig.SIGNAL_SFU_URL
    }
    set(value) = putString(CALLING_SERVER, value)

  /**
   * Setting to override the default calling bandwidth mode.
   */
  val callingDataMode: DataMode
    get() {
      return if (RemoteConfig.internalUser) {
        val index = getInteger(CALLING_DATA_MODE, DataMode.NORMAL.ordinal)
        val modes: Array<DataMode> = DataMode.entries.toTypedArray()

        if (index < modes.size) modes[index] else DataMode.NORMAL
      } else {
        DataMode.NORMAL
      }
    }

  /**
   * Whether or not Telecom integration is manually disabled.
   */
  var callingDisableTelecom by booleanValue(CALLING_DISABLE_TELECOM, true).falseForExternalUsers()

  /**
   * Whether or not to override the audio settings from the remote configuration.
   */
  var callingSetAudioConfig by booleanValue(CALLING_SET_AUDIO_CONFIG, false).falseForExternalUsers()

  /**
   * If overriding the audio settings, use the Oboe ADM or not.
   */
  var callingUseOboeAdm by booleanValue(CALLING_USE_OBOE_ADM, true).defaultForExternalUsers()

  /**
   * If overriding the audio settings, use the Software AEC or not.
   */
  var callingUseSoftwareAec by booleanValue(CALLING_USE_SOFTWARE_AEC, false).defaultForExternalUsers()

  /**
   * If overriding the audio settings, use the Software NS or not.
   */
  var callingUseSoftwareNs by booleanValue(CALLING_USE_SOFTWARE_NS, false).defaultForExternalUsers()

  /**
   * If overriding the audio settings, use Low Latency for the input or not.
   */
  var callingUseInputLowLatency by booleanValue(CALLING_USE_INPUT_LOW_LATENCY, true).defaultForExternalUsers()

  /**
   * If overriding the audio settings, use Voice Comm for the input or not.
   */
  var callingUseInputVoiceComm by booleanValue(CALLING_USE_INPUT_VOICE_COMM, true).defaultForExternalUsers()

  /**
   * Whether or not to override the video settings from the remote configuration.
   */
  var callingSetVideoConfig by booleanValue(CALLING_SET_VIDEO_CONFIG, false).falseForExternalUsers()

  /**
   * If overriding the video settings, use hardware VP9 encoder or not if available
   */
  var callingUseHardwareVp9Encode by booleanValue(CALLING_USE_HARDWARE_VP9_ENCODE, true).defaultForExternalUsers()

  /**
   * If overriding the video settings, use hardware VP9 decoder or not if available
   */
  var callingUseHardwareVp9Decode by booleanValue(CALLING_USE_HARDWARE_VP9_DECODE, true).defaultForExternalUsers()

  /**
   * If overriding the video settings, use software VP9 encoder or not
   */
  var callingUseSoftwareVp9Encode by booleanValue(CALLING_USE_SOFTWARE_VP9_ENCODE, true).defaultForExternalUsers()

  /**
   * If overriding the video settings, use software VP9 encoder or not
   */
  var callingUseSoftwareVp9Decode by booleanValue(CALLING_USE_SOFTWARE_VP9_DECODE, true).defaultForExternalUsers()

  var lastScrollPosition: Int by integerValue(LAST_SCROLL_POSITION, 0).defaultForExternalUsers()

  var useConversationItemV2Media by booleanValue(CONVERSATION_ITEM_V2_MEDIA, false).defaultForExternalUsers()

  var forceSsre2Capability by booleanValue("internal.force_ssre2_capability", false).defaultForExternalUsers()

  /**
   * The minimum [IssuePriority] that an issue recorded by [org.thoughtcrime.securesms.database.IssueReporter] must have
   * in order to raise a user notification.
   */
  var issueNotificationPriority: IssuePriority
    get() = IssuePriority.fromValue(getInteger(ISSUE_NOTIFICATION_PRIORITY, IssuePriority.HIGH.value))
    set(value) = putInteger(ISSUE_NOTIFICATION_PRIORITY, value.value)

  var showArchiveStateHint by booleanValue(SHOW_ARCHIVE_STATE_HINT, false).defaultForExternalUsers()

  /** Whether or not we should include a debuglog in the backup debug info when generating a backup. */
  var includeDebuglogInBackup by booleanValue(INCLUDE_DEBUGLOG_IN_BACKUP, true).falseForExternalUsers()

  /** Any [BackupDebugInfo] that was imported during the last backup restore, if any. */
  var importedBackupDebugInfo: BackupDebugInfo? by protoValue(IMPORTED_BACKUP_DEBUG_INFO, BackupDebugInfo.ADAPTER).defaultForExternalUsers()

  /** Enable ANR detector forcing a crash. */
  var anrDetectionCrashes by booleanValue(ANR_DETECTION_CRASH, true).falseForExternalUsers()

  private fun <T> SignalStoreValueDelegate<T>.defaultForExternalUsers(): SignalStoreValueDelegate<T> {
    return this.withPrecondition { RemoteConfig.internalUser }
  }

  private fun SignalStoreValueDelegate<Boolean>.falseForExternalUsers(): SignalStoreValueDelegate<Boolean> {
    return this.map { actualValue -> RemoteConfig.internalUser && actualValue }
  }
}
