package ch.threema.app.tasks

import ch.threema.app.multidevice.MultiDeviceManager
import ch.threema.app.services.UserService
import ch.threema.base.crypto.NonceFactory
import ch.threema.base.utils.getThreemaLogger
import ch.threema.domain.protocol.csp.ProtocolDefines
import ch.threema.domain.taskmanager.ActiveTask
import ch.threema.domain.taskmanager.ActiveTaskCodec
import ch.threema.domain.taskmanager.TRANSACTION_TTL_MAX
import ch.threema.domain.taskmanager.Task
import ch.threema.domain.taskmanager.TaskCodec
import ch.threema.domain.taskmanager.createTransaction
import ch.threema.domain.taskmanager.getEncryptedUserProfileSyncUpdate
import ch.threema.protobuf.common.Image
import ch.threema.protobuf.common.blob
import ch.threema.protobuf.common.deltaImage
import ch.threema.protobuf.common.image
import ch.threema.protobuf.common.unit
import ch.threema.protobuf.d2d.TransactionScope
import ch.threema.protobuf.d2d.sync.userProfile
import ch.threema.storage.models.ContactModel
import com.google.protobuf.kotlin.toByteString
import kotlinx.serialization.Serializable
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

private val logger = getThreemaLogger("ReflectUserProfilePictureSyncTask")

/**
 * This task just reflects the currently stored user profile picture. This is a simple mechanism
 * that is not optimal when other devices also support user profile picture changes as in case of a
 * sync race condition a reflected profile picture may be reflected back. The advantage of this
 * approach is its simplicity and the fact that this task can be scheduled and run without causing
 * any damage.
 */
class ReflectUserProfilePictureSyncTask() : ActiveTask<Unit>, PersistableTask, KoinComponent {
    private val multiDeviceManager: MultiDeviceManager by inject()
    private val nonceFactory: NonceFactory by inject()
    private val userService: UserService by inject()

    private val mdProperties by lazy { multiDeviceManager.propertiesProvider.get() }

    override val type = "ReflectUserProfilePictureSyncTask"

    override suspend fun invoke(handle: ActiveTaskCodec) {
        if (!multiDeviceManager.isMultiDeviceActive) {
            logger.warn("Cannot reflect profile picture because multi device is not active")
            return
        }

        val profilePictureUploadData = userService.uploadUserProfilePictureOrGetPreviousUploadData()

        val profilePictureUpdate = deltaImage {
            if (profilePictureUploadData.blobId.contentEquals(ContactModel.NO_PROFILE_PICTURE_BLOB_ID)) {
                removed = unit { }
            } else {
                updated = image {
                    type = Image.Type.JPEG
                    blob = blob {
                        id = profilePictureUploadData.blobId.toByteString()
                        nonce = ProtocolDefines.CONTACT_PHOTO_NONCE.toByteString()
                        key = profilePictureUploadData.encryptionKey.toByteString()
                        uploadedAt = profilePictureUploadData.uploadedAt
                    }
                }
            }
        }

        handle.createTransaction(
            keys = mdProperties.keys,
            scope = TransactionScope.Scope.USER_PROFILE_SYNC,
            ttl = TRANSACTION_TTL_MAX,
        ).execute {
            val encryptedEnvelopeResult = getEncryptedUserProfileSyncUpdate(
                userProfile = userProfile {
                    profilePicture = profilePictureUpdate
                },
                mdProperties,
            )

            handle.reflectAndAwaitAck(encryptedEnvelopeResult, true, nonceFactory)
        }
    }

    override fun serialize(): SerializableTaskData = ReflectUserProfilePictureSyncTaskData

    @Serializable
    data object ReflectUserProfilePictureSyncTaskData : SerializableTaskData {
        override fun createTask(): Task<*, TaskCodec> =
            ReflectUserProfilePictureSyncTask()
    }
}
