package ch.threema.domain.protocol.csp.coders;

import ch.threema.base.ThreemaException;
import ch.threema.domain.models.MessageId;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.*;
import java.time.Instant;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static ch.threema.common.JavaCompat.hexToByteArray;

public class MessageBoxTest {

    private static final long TEST_DATE_UNIX = 1620000000;

    private static final String TEST_FROM_IDENTITY = "AAAAAAAA";
    private static final String TEST_TO_IDENTITY = "BBBBBBBB";
    private static final byte[] TEST_MESSAGE_ID = hexToByteArray("0001020304050607");
    private static final Instant TEST_DATE = Instant.ofEpochMilli(TEST_DATE_UNIX * 1000);
    private static final String TEST_NICKNAME = "Nickname";
    private static final byte[] TEST_METADATA_BOX = hexToByteArray("000102030405060708090a0b0c0d0e0f101112");
    private static final byte[] TEST_NONCE = hexToByteArray("000102030405060708090a0b0c0d0e0f1011121314151617");
    private static final byte[] TEST_BOX = hexToByteArray("000102030405060708090a0b0c0d0e0f10111213");

    private static final byte[] EXPECTED_BINARY = hexToByteArray(
        "414141414141414142424242424242420001020304050607003d8f6000001300" +
            "4e69636b6e616d65000000000000000000000000000000000000000000000000" +
            "000102030405060708090a0b0c0d0e0f101112000102030405060708090a0b0c" +
            "0d0e0f1011121314151617000102030405060708090a0b0c0d0e0f10111213"
    );

    @Test
    void testSerializeDeserialize() throws Exception {
        // Encode message and check expected binary output
        MessageBox testMessage = getTestMessage();

        byte[] binary = testMessage.makeBinary();
        Assertions.assertArrayEquals(EXPECTED_BINARY, binary);

        // Decode message again
        MessageBox decodedMessage = MessageBox.parseBinary(binary);

        // Check decoded fields
        Assertions.assertEquals(TEST_FROM_IDENTITY, decodedMessage.getFromIdentity());
        Assertions.assertEquals(TEST_TO_IDENTITY, decodedMessage.getToIdentity());
        Assertions.assertArrayEquals(TEST_MESSAGE_ID, decodedMessage.getMessageId().getMessageId());
        Assertions.assertEquals(TEST_DATE, decodedMessage.getTimestamp());
        Assertions.assertEquals(0, decodedMessage.getFlags());
        Assertions.assertEquals(TEST_NICKNAME, decodedMessage.getPushFromName());
        Assertions.assertArrayEquals(TEST_METADATA_BOX, decodedMessage.getMetadataBox().getBox());
        Assertions.assertArrayEquals(TEST_NONCE, decodedMessage.getNonce());
        Assertions.assertArrayEquals(TEST_BOX, decodedMessage.getBox());
    }

    @Test
    void testJavaSerializable() {
        // Ensure BoxedMessage objects are Serializable
        assertDoesNotThrow(() -> {
            MessageBox testMessage = getTestMessage();
            ObjectOutputStream oos = new ObjectOutputStream(new OutputStream() {
                @Override
                public void write(int b) {
                    // ignore
                }
            });
            oos.writeObject(testMessage);
            oos.close();
        });
    }

    private MessageBox getTestMessage() throws ThreemaException {
        // Encode message and check expected binary output
        MessageBox testMessage = new MessageBox();

        MessageId messageId = new MessageId(TEST_MESSAGE_ID);

        testMessage.setFromIdentity(TEST_FROM_IDENTITY);
        testMessage.setToIdentity(TEST_TO_IDENTITY);
        testMessage.setMessageId(messageId);
        testMessage.setTimestamp(TEST_DATE);
        testMessage.setFlags(0);
        testMessage.setPushFromName(TEST_NICKNAME);
        testMessage.setMetadataBox(new MetadataBox(TEST_METADATA_BOX));
        testMessage.setNonce(TEST_NONCE);
        testMessage.setBox(TEST_BOX);

        return testMessage;
    }
}
