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

import ch.threema.base.utils.getThreemaLogger
import ch.threema.domain.models.GroupId
import ch.threema.domain.protocol.csp.ProtocolDefines
import ch.threema.domain.protocol.csp.messages.AbstractGroupMessage
import ch.threema.domain.protocol.csp.messages.BadMessageException
import ch.threema.domain.types.IdentityString
import ch.threema.protobuf.csp.e2e.fs.Version
import ch.threema.protobuf.d2d.IncomingMessage
import ch.threema.protobuf.d2d.OutgoingMessage
import java.io.ByteArrayOutputStream
import java.nio.charset.StandardCharsets
import org.json.JSONArray
import org.json.JSONException

private val logger = getThreemaLogger("GroupPollVoteMessage")

/**
 * A group poll vote message.
 */
class GroupPollVoteMessage : AbstractGroupMessage(), PollVoteInterface {
    override var pollId: PollId? = null
    override var pollCreatorIdentity: IdentityString? = null
    override val votes: MutableList<PollVote> = mutableListOf()

    override fun getMinimumRequiredForwardSecurityVersion(): Version = Version.V1_2

    override fun allowUserProfileDistribution(): Boolean = true

    override fun exemptFromBlocking(): Boolean = true

    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 addVotes(votes: List<PollVote>) {
        this.votes.addAll(votes)
    }

    @Throws(BadMessageException::class)
    fun parseVotes(votes: String?) {
        try {
            val votesJsonArray = JSONArray(votes)
            for (voteJson in 0 until votesJsonArray.length()) {
                this.votes.add(PollVote.parse(votesJsonArray.getJSONArray(voteJson)))
            }
        } catch (_: JSONException) {
            throw BadMessageException("TM035")
        }
    }

    override fun getBody(): ByteArray? {
        if (pollCreatorIdentity == null || pollId == null) {
            return null
        }
        try {
            val bos = ByteArrayOutputStream()

            bos.write(groupCreator.toByteArray(StandardCharsets.US_ASCII))
            bos.write(apiGroupId.groupId)
            bos.write(pollCreatorIdentity!!.toByteArray(StandardCharsets.US_ASCII))
            bos.write(pollId!!.pollId)

            val jsonArrayVotes = JSONArray()
            for (vote in votes) {
                jsonArrayVotes.put(vote.jsonArray)
            }
            bos.write(jsonArrayVotes.toString().toByteArray(StandardCharsets.US_ASCII))
            return bos.toByteArray()
        } catch (exception: Exception) {
            logger.error(exception.message)
            return null
        }
    }

    override fun getType(): Int = ProtocolDefines.MSGTYPE_GROUP_POLL_VOTE

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

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

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

        /**
         * Get the group poll-vote 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 poll-vote message
         * @throws BadMessageException if the length is invalid
         */
        @JvmStatic
        @Throws(BadMessageException::class)
        fun fromByteArray(data: ByteArray, offset: Int, length: Int): GroupPollVoteMessage {
            if (length < 1) {
                throw BadMessageException("Bad length ($length) for poll vote message")
            } else if (offset < 0) {
                throw BadMessageException("Bad offset ($offset) for poll vote message")
            } else if (data.size < length + offset) {
                throw BadMessageException("Invalid byte array length (${data.size}) for offset $offset and length $length")
            }

            return GroupPollVoteMessage().apply {
                var positionIndex = offset
                groupCreator = String(
                    data,
                    positionIndex,
                    ProtocolDefines.IDENTITY_LEN,
                    StandardCharsets.US_ASCII,
                )
                positionIndex += ProtocolDefines.IDENTITY_LEN

                apiGroupId = GroupId(data, positionIndex)
                positionIndex += ProtocolDefines.GROUP_ID_LEN

                pollCreatorIdentity = String(
                    data,
                    positionIndex,
                    ProtocolDefines.IDENTITY_LEN,
                    StandardCharsets.US_ASCII,
                )
                positionIndex += ProtocolDefines.IDENTITY_LEN

                pollId = PollId(data, positionIndex)
                positionIndex += ProtocolDefines.POLL_ID_LEN

                parseVotes(
                    String(
                        data,
                        positionIndex,
                        length + offset - positionIndex,
                        StandardCharsets.UTF_8,
                    ),
                )
            }
        }
    }
}
