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

import ch.threema.domain.protocol.csp.messages.BadMessageException;
import ch.threema.domain.protocol.csp.messages.voip.features.CallFeature;
import ch.threema.domain.protocol.csp.messages.voip.features.FeatureList;
import ch.threema.domain.protocol.csp.messages.voip.features.VideoFeature;

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

import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.stream.Collectors;

public class VoipCallOfferDataTest {

    /**
     * A valid offer.
     */
    @Test
    public void testValidOffer() throws Exception {
        final VoipCallOfferData.OfferData offerData = new VoipCallOfferData.OfferData()
            .setSdpType("offer")
            .setSdp("sdpsdp");

        final VoipCallOfferData msg = new VoipCallOfferData()
            .setOfferData(offerData);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        msg.write(bos);
        final String json = bos.toString();

        Assertions.assertEquals("{\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"}}", json);
    }

    @Test
    public void testToString() {
        final VoipCallOfferData.OfferData offerData = new VoipCallOfferData.OfferData();
        offerData.setSdpType("offer");
        offerData.setSdp("sdpsdp");
        Assertions.assertEquals("OfferData{sdpType='offer', sdp='sdpsdp'}", offerData.toString());
    }

    /**
     * A valid offer (rollback -> sdp is null).
     */
    @Test
    public void testRollbackOffer() throws Exception {
        final VoipCallOfferData.OfferData offerData = new VoipCallOfferData.OfferData();
        offerData.setSdpType("rollback");
        //noinspection ConstantConditions
        offerData.setSdp(null);

        final VoipCallOfferData msg = new VoipCallOfferData();
        msg.setOfferData(offerData);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        msg.write(bos);
        final String json = bos.toString();

        Assertions.assertEquals("{\"offer\":{\"sdpType\":\"rollback\",\"sdp\":null}}", json);
    }

    /**
     * Offer sdpType = answer
     */
    @Test
    public void parseOfferTypeAnswer() {
        try {
            VoipCallOfferData.parse(
                "{\"offer\":{\"sdpType\":\"answer\",\"sdp\":\"sdpsdp\"}}"
            );
            Assertions.fail("BadMessageException not thrown");
        } catch (BadMessageException e) { /* ok */ }
    }

    /**
     * Offer sdpType = pranswer
     */
    @Test
    public void parseOfferTypePranswer() {
        try {
            VoipCallOfferData.parse(
                "{\"offer\":{\"sdpType\":\"pranswer\",\"sdp\":\"sdpsdp\"}}"
            );
            Assertions.fail("BadMessageException not thrown");
        } catch (BadMessageException e) { /* ok */ }
    }

    /**
     * Offer with sdpType = offer and sdp = null
     */
    @Test
    public void parseOfferSdpNull() {
        try {
            VoipCallOfferData.parse(
                "{\"offer\":{\"sdpType\":\"offer\",\"sdp\":null}}"
            );
            Assertions.fail("BadMessageException not thrown");
        } catch (BadMessageException e) { /* ok */ }
    }

    /**
     * Accept answer with sdpType = rollback and sdp = null
     */
    @Test
    public void parseOfferTypeRollback() throws BadMessageException {
        VoipCallOfferData.parse(
            "{\"offer\":{\"sdpType\":\"rollback\",\"sdp\":null}}"
        );
    }

    @Test
    public void createOfferWithFeatures() throws Exception {
        final VoipCallOfferData.OfferData offerData = new VoipCallOfferData.OfferData()
            .setSdpType("offer")
            .setSdp("sdpsdp");

        final VoipCallOfferData msg = new VoipCallOfferData()
            .setOfferData(offerData)
            .addFeature(new VideoFeature());

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        msg.write(bos);
        final String json = bos.toString();

        Assertions.assertEquals("{\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"},\"features\":{\"video\":null}}", json);
    }

    @Test
    public void parseOfferWithoutFeatures() throws BadMessageException {
        final VoipCallOfferData parsed = VoipCallOfferData.parse(
            "{\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"}}"
        );
        Assertions.assertNotNull(parsed.getOfferData());
        Assertions.assertEquals("offer", parsed.getOfferData().getSdpType());
        Assertions.assertTrue(parsed.getFeatures().isEmpty());
    }

    @Test
    public void parseOfferWithFeatures() throws BadMessageException {
        final VoipCallOfferData parsed = VoipCallOfferData.parse(
            "{" +
                "\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"}," +
                "\"features\":{" +
                "\"video\":null," +
                "\"superextension\":{\"a\":1}" +
                "}" +
                "}"
        );
        Assertions.assertNotNull(parsed.getOfferData());
        Assertions.assertEquals("offer", parsed.getOfferData().getSdpType());
        final FeatureList features = parsed.getFeatures();
        Assertions.assertEquals(2, features.size());

        final List<String> names = features
            .getList()
            .stream()
            .map(CallFeature::getName)
            .sorted()
            .collect(Collectors.toList());
        Assertions.assertEquals("superextension", names.get(0));
        Assertions.assertEquals("video", names.get(1));
    }

    @Test
    public void createOfferWithCallId() throws Exception {
        final VoipCallOfferData.OfferData offerData = new VoipCallOfferData.OfferData()
            .setSdpType("offer")
            .setSdp("sdpsdp");

        final VoipCallOfferData msg = new VoipCallOfferData()
            .setCallId(1337)
            .setOfferData(offerData);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        msg.write(bos);
        final String json = bos.toString();

        Assertions.assertEquals("{\"callId\":1337,\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"}}", json);
    }

    @Test
    public void validateCallIdZero() {
        // 0 is is valid
        new VoipCallOfferData().setCallId(0);
    }

    @Test
    public void validateCallIdNegative() {
        try {
            // Call ID must be positive
            new VoipCallOfferData().setCallId(-1);
            Assertions.fail("IllegalArgumentException not thrown");
        } catch (IllegalArgumentException e) { /* ok */ }
    }

    @Test
    public void validateCallIdTooLarge() {
        try {
            // Call ID must fit in a 32 bit integer
            new VoipCallOfferData().setCallId(4294967296L);
            Assertions.fail("IllegalArgumentException not thrown");
        } catch (IllegalArgumentException e) { /* ok */ }
    }

    @Test
    public void parseOfferWithCallId() throws BadMessageException {
        final VoipCallOfferData parsed = VoipCallOfferData.parse(
            "{" +
                "\"callId\":1337," +
                "\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"}" +
                "}"
        );
        Assertions.assertNotNull(parsed.getOfferData());
        Assertions.assertEquals("offer", parsed.getOfferData().getSdpType());
        Assertions.assertEquals(Long.valueOf(1337), parsed.getCallId());
    }

    @Test
    public void parseOfferWithoutCallId() throws BadMessageException {
        final VoipCallOfferData parsed = VoipCallOfferData.parse(
            "{\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"}}"
        );
        Assertions.assertNotNull(parsed.getOfferData());
        Assertions.assertEquals("offer", parsed.getOfferData().getSdpType());
        Assertions.assertNull(parsed.getCallId());
    }

    @Test
    public void parseOfferWithCallIdNegative() {
        try {
            VoipCallOfferData.parse(
                "{" +
                    "\"callId\":-1," +
                    "\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"}" +
                    "}"
            );
            Assertions.fail("BadMessageException not thrown");
        } catch (BadMessageException e) { /* ok */ }
    }

    @Test
    public void parseOfferWithCallIdTooLarge() {
        try {
            // Call ID fits in a long, but is larger than 2**32 (which is disallowed by the spec)
            VoipCallOfferData.parse(
                "{" +
                    "\"callId\":4294967297," +
                    "\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"}" +
                    "}"
            );
            Assertions.fail("BadMessageException not thrown");
        } catch (BadMessageException e) { /* ok */ }
    }

    @Test
    public void parseOfferWithCallIdOutOfRange() {
        try {
            // Call ID does not fit in a long
            VoipCallOfferData.parse(
                "{" +
                    "\"callId\":1180591620717411303424," +
                    "\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"}" +
                    "}"
            );
            Assertions.fail("BadMessageException not thrown");
        } catch (BadMessageException e) { /* ok */ }
    }

    @Test
    public void parseOfferWithCallIdBoolean() {
        try {
            VoipCallOfferData.parse(
                "{" +
                    "\"callId\":false," +
                    "\"offer\":{\"sdpType\":\"offer\",\"sdp\":\"sdpsdp\"}" +
                    "}"
            );
            Assertions.fail("BadMessageException not thrown");
        } catch (BadMessageException e) { /* ok */ }
    }
}
