package ch.threema.domain.protocol.csp.messages

import ch.threema.base.utils.getThreemaLogger
import ch.threema.common.buildByteArray
import ch.threema.common.emptyByteArray
import ch.threema.common.writeLittleEndianInt
import ch.threema.domain.protocol.csp.ProtocolDefines
import ch.threema.protobuf.csp.e2e.fs.Version
import ch.threema.protobuf.d2d.IncomingMessage
import ch.threema.protobuf.d2d.OutgoingMessage
import java.nio.ByteBuffer
import java.nio.ByteOrder

private val logger = getThreemaLogger("ContactSetPhotoMessage")

/**
 * A profile picture uploaded as a blob
 *
 * The contents are referenced by the `blobId`, the file `size` in bytes,
 * and the nonce to be used when decrypting the image blob.
 */
class SetProfilePictureMessage(
    @JvmField
    val blobId: ByteArray,
    @JvmField
    val size: Int,
    @JvmField
    val encryptionKey: ByteArray,
) : AbstractMessage() {
    override fun getType(): Int {
        return ProtocolDefines.MSGTYPE_CONTACT_SET_PHOTO
    }

    override fun getMinimumRequiredForwardSecurityVersion(): Version = Version.V1_1

    override fun allowUserProfileDistribution(): Boolean = false

    override fun exemptFromBlocking(): Boolean = false

    override fun createImplicitlyDirectContact(): Boolean = false

    override fun protectAgainstReplay(): Boolean = true

    override fun reflectIncoming(): Boolean = true

    override fun reflectOutgoing(): Boolean = true

    override fun reflectSentUpdate(): Boolean = false

    override fun sendAutomaticDeliveryReceipt(): Boolean = false

    override fun bumpLastUpdate(): Boolean = false

    override fun getBody(): ByteArray =
        try {
            buildByteArray {
                write(blobId)
                writeLittleEndianInt(size)
                write(encryptionKey)
            }
        } catch (e: Exception) {
            logger.error(e.message)
            emptyByteArray()
        }

    companion object {
        @JvmStatic
        fun fromReflected(message: IncomingMessage): SetProfilePictureMessage = fromByteArray(
            data = message.body.toByteArray(),
        ).apply {
            initializeCommonProperties(message)
        }

        @JvmStatic
        fun fromReflected(message: OutgoingMessage): SetProfilePictureMessage = fromByteArray(
            data = message.body.toByteArray(),
        ).apply {
            initializeCommonProperties(message)
        }

        @JvmStatic
        @Throws(BadMessageException::class)
        fun fromByteArray(data: ByteArray): SetProfilePictureMessage = fromByteArray(
            data = data,
            offset = 0,
            length = data.size,
        )

        /**
         * Get the set profile picture message from the given array.
         *
         * @param data   the data that represents the message
         * @param offset the offset where the data starts
         * @param length the length of the data (needed to ignore the padding)
         * @return the set profile picture message
         * @throws BadMessageException if the length is invalid
         */
        @JvmStatic
        @Throws(BadMessageException::class)
        fun fromByteArray(data: ByteArray, offset: Int, length: Int): SetProfilePictureMessage {
            // Blob size is an int (4 bytes)
            val blobSizeLength = 4
            when {
                length != ProtocolDefines.BLOB_ID_LEN + blobSizeLength + ProtocolDefines.BLOB_KEY_LEN -> {
                    throw BadMessageException("Bad length ($length) for set profile picture message")
                }

                offset < 0 -> {
                    throw BadMessageException("Bad offset ($offset) for set profile picture message")
                }

                data.size < length + offset -> {
                    throw BadMessageException("Invalid byte array length (${data.size}) for offset $offset and length $length")
                }
            }
            var readOffset = offset

            val blobId = data.copyOfRange(readOffset, readOffset + ProtocolDefines.BLOB_ID_LEN)
            readOffset += ProtocolDefines.BLOB_ID_LEN

            val blobSize = ByteBuffer.wrap(data, readOffset, blobSizeLength)
                .order(ByteOrder.LITTLE_ENDIAN)
                .getInt()
            readOffset += blobSizeLength

            val encryptionKey =
                data.copyOfRange(readOffset, readOffset + ProtocolDefines.BLOB_KEY_LEN)

            return SetProfilePictureMessage(
                blobId = blobId,
                size = blobSize,
                encryptionKey = encryptionKey,
            )
        }
    }
}
