/*
 * Copyright 2026 Signal Messenger, LLC
 * SPDX-License-Identifier: AGPL-3.0-only
 */

package org.thoughtcrime.securesms.groups.memberlabel

import android.content.Context
import androidx.annotation.WorkerThread
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.signal.core.models.ServiceId
import org.signal.core.util.orNull
import org.signal.network.NetworkResult
import org.thoughtcrime.securesms.conversation.colors.ColorizerV2
import org.thoughtcrime.securesms.conversation.colors.NameColor
import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.GroupAccessControl
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.keyvalue.UiHintValues
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.recipients.RecipientUtil

/**
 * Handles the retrieval and modification of group member labels.
 */
class MemberLabelRepository private constructor(
  private val context: Context = AppDependencies.application,
  private val groupsTable: GroupTable = SignalDatabase.groups,
  private val uiHints: UiHintValues = SignalStore.uiHints
) {
  companion object {
    @JvmStatic
    val instance: MemberLabelRepository by lazy { MemberLabelRepository() }
  }

  suspend fun getRecipient(recipientId: RecipientId): Recipient = withContext(Dispatchers.Default) {
    Recipient.resolved(recipientId)
  }

  /**
   * Gets the member label for a specific recipient in the group.
   */
  suspend fun getLabel(groupId: GroupId.V2, recipientId: RecipientId): MemberLabel? {
    return getLabel(groupId, getRecipient(recipientId))
  }

  /**
   * Gets the member label for a specific recipient in the group (blocking version for Java compatibility).
   */
  @WorkerThread
  fun getLabelSync(groupId: GroupId.V2, recipient: Recipient): MemberLabel? {
    val aci = recipient.serviceId.orNull() as? ServiceId.ACI ?: return null
    val groupRecord = groupsTable.getGroup(groupId).orNull() ?: return null
    if (!isSelfAnActiveGroupMember(groupRecord)) return null

    return groupRecord.requireV2GroupProperties().memberLabel(aci)?.sanitized()
  }

  /**
   * Gets member labels for a list of recipients in a group (blocking version for Java compatibility).
   */
  @WorkerThread
  fun getLabelsSync(groupId: GroupId.V2, recipients: Collection<Recipient>): Map<RecipientId, MemberLabel> {
    val groupRecord = groupsTable.getGroup(groupId).orNull() ?: return emptyMap()
    if (!isSelfAnActiveGroupMember(groupRecord)) return emptyMap()

    val labelsByAci = groupRecord.requireV2GroupProperties().memberLabelsByAci()
    return buildMap {
      recipients.forEach { recipient ->
        val aci = recipient.serviceId.orNull() as? ServiceId.ACI
        labelsByAci[aci]?.let { label -> put(recipient.id, label.sanitized()) }
      }
    }
  }

  /**
   * Gets the member label for a specific recipient in the group.
   */
  suspend fun getLabel(groupId: GroupId.V2, recipient: Recipient): MemberLabel? = withContext(Dispatchers.Default) {
    getLabelSync(groupId, recipient)
  }

  /**
   * Gets member labels for a list of recipients in a group.
   *
   * Returns a map of [RecipientId] to [MemberLabel] for members that have labels.
   */
  suspend fun getLabels(groupId: GroupId.V2, recipients: List<Recipient>): Map<RecipientId, MemberLabel> = withContext(Dispatchers.Default) {
    getLabelsSync(groupId, recipients)
  }

  /**
   * Checks whether [recipient] has permission to set their member label in the given group.
   */
  suspend fun canSetLabel(groupId: GroupId.V2, recipient: Recipient): Boolean = withContext(Dispatchers.Default) {
    val groupRecord = groupsTable.getGroup(groupId).orNull() ?: return@withContext false

    if (!isSelfAnActiveGroupMember(groupRecord)) return@withContext false
    if (groupRecord.isTerminated) return@withContext false

    val memberLevel = groupRecord.memberLevel(recipient)
    if (groupRecord.memberLabelAccessControl == GroupAccessControl.ONLY_ADMINS) {
      memberLevel == GroupTable.MemberLevel.ADMINISTRATOR
    } else {
      memberLevel.isInGroup
    }
  }

  /**
   * Computes the sender [NameColor] for a recipient as seen by other group members.
   */
  suspend fun getSenderNameColor(groupId: GroupId.V2, recipient: Recipient): NameColor = withContext(Dispatchers.Default) {
    val groupMemberIds = groupsTable
      .getGroupMembers(groupId, GroupTable.MemberSet.FULL_MEMBERS_INCLUDING_SELF)
      .mapNotNull { it.serviceId.orNull() }

    ColorizerV2(groupMemberIds).getNameColor(context, recipient)
  }

  /**
   * Returns all group members who have labels set for the given group.
   */
  suspend fun getMembersWithLabels(groupId: GroupId.V2): List<GroupMemberWithLabel> = withContext(Dispatchers.Default) {
    val groupRecord = groupsTable.getGroup(groupId).orNull() ?: return@withContext emptyList()
    if (!isSelfAnActiveGroupMember(groupRecord)) return@withContext emptyList()

    val groupProperties = groupRecord.requireV2GroupProperties()
    val allMembers = groupProperties.getMemberRecipients(GroupTable.MemberSet.FULL_MEMBERS_INCLUDING_SELF)
    val colorizer = ColorizerV2(groupMemberIds = allMembers.mapNotNull { it.serviceId.orNull() })
    val labelsByAci = groupProperties.memberLabelsByAci()

    allMembers.mapNotNull { member ->
      val aci = member.serviceId.orNull() as? ServiceId.ACI
      val label = labelsByAci[aci]?.sanitized() ?: return@mapNotNull null

      GroupMemberWithLabel(
        recipient = member,
        isAdmin = groupProperties.isAdmin(member),
        label = label,
        nameColor = colorizer.getNameColor(context, member)
      )
    }
  }

  /**
   * Sets the group member label for the current user.
   */
  suspend fun setLabel(groupId: GroupId.V2, label: MemberLabel): NetworkResult<Unit> = withContext(Dispatchers.IO) {
    val sanitizedLabel = label.sanitized()
    NetworkResult.fromFetch {
      GroupManager.updateMemberLabel(context, groupId, sanitizedLabel.text, sanitizedLabel.emoji.orEmpty())
    }
  }

  fun hasDismissedMemberLabelAboutOverrideWarning(): Boolean {
    return uiHints.hasDismissedMemberLabelAboutOverrideWarning()
  }

  fun markMemberLabelAboutOverrideWarningDismissed() {
    uiHints.markMemberLabelAboutOverrideWarningDismissed()
  }

  @WorkerThread
  private fun isSelfAnActiveGroupMember(groupRecord: GroupRecord): Boolean {
    return when {
      !groupRecord.memberLevel(Recipient.self()).isInGroup -> false
      else -> RecipientUtil.isMessageRequestAccepted(Recipient.resolved(groupRecord.recipientId))
    }
  }
}

private fun MemberLabel.sanitized(): MemberLabel = this.copy(
  emoji = MemberLabel.sanitizeEmoji(this.emoji),
  text = MemberLabel.sanitizeLabelText(this.text)
)
