package ch.threema.domain.taskmanager

import ch.threema.base.utils.getThreemaLogger
import ch.threema.domain.protocol.connection.PayloadProcessingException
import ch.threema.domain.protocol.connection.data.CspMessage
import ch.threema.domain.protocol.csp.ProtocolDefines
import ch.threema.domain.protocol.csp.coders.MessageBox

private val logger = getThreemaLogger("IncomingCspMessageTask")

class IncomingCspMessageTask(
    private val message: CspMessage,
    private val incomingMessageProcessor: IncomingMessageProcessor,
) : ActiveTask<Unit> {
    override val type: String = "IncomingCspMessageTask"

    override suspend fun invoke(handle: ActiveTaskCodec) {
        when (message.payloadType.toInt()) {
            ProtocolDefines.PLTYPE_ERROR -> processError(message.toServerErrorData())
            ProtocolDefines.PLTYPE_ALERT -> processAlert(message.toServerAlertData())
            ProtocolDefines.PLTYPE_OUTGOING_MESSAGE_ACK -> processOutgoingMessageAck(message.toOutgoingMessageAck())
            ProtocolDefines.PLTYPE_INCOMING_MESSAGE -> processIncomingMessage(
                message.toIncomingMessageData(),
                handle,
            )

            ProtocolDefines.PLTYPE_QUEUE_SEND_COMPLETE -> processQueueSendComplete()
            ProtocolDefines.PLTYPE_DEVICE_COOKIE_CHANGE_INDICATION -> processDeviceCookieChangeIndication()
            else -> throw PayloadProcessingException("Unknown payload type ${message.payloadType}")
        }
    }

    private fun processError(data: CspMessage.ServerErrorData) {
        // Note that server errors are already processed in the task manager
        logger.warn("Processed server error in incoming csp message task: '{}'", data.message)
    }

    private fun processAlert(data: CspMessage.ServerAlertData) {
        // Note that server alerts are already processed in the task manager
        logger.warn("Processed server alert in incoming csp message task: '{}'", data.message)
    }

    private fun processOutgoingMessageAck(data: CspMessage.MessageAck) {
        // Note that outgoing message acks should be consumed in the task that sent out the message
        logger.warn(
            "Processed ack for outgoing message {} to {} in incoming csp message task",
            data.messageId,
            data.recipient,
        )
    }

    private suspend fun processIncomingMessage(
        data: CspMessage.IncomingMessageData,
        handle: ActiveTaskCodec,
    ) {
        // TODO(ANDR-2732): If multi device is active and the device is not the leader: abort and restart connection

        if (data.data.size < ProtocolDefines.OVERHEAD_MSG_HDR) {
            throw PayloadProcessingException("Bad length (${data.data.size}) for message payload")
        }
        suspend {
            val messageBox = MessageBox.parseBinary(data.data)

            incomingMessageProcessor.processIncomingCspMessage(messageBox, handle)
        }.catchAllExceptNetworkException {
            logger.error("Could not process incoming message", it)
        }
    }

    private fun processQueueSendComplete() {
        // Note that queue send complete messages are already processed in the task manager
        logger.warn("Processed queue send complete inside incoming csp message task")
    }

    private fun processDeviceCookieChangeIndication() {
        // Note that device cookie change indications are already processed in the task manager
        logger.warn("Processed device cookie change indication inside incoming csp message task")
    }
}
