////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation                                             //
//                                                                            //
// Use of this source code is governed by a license that can be found in the  //
// LICENSE file.                                                              //
////////////////////////////////////////////////////////////////////////////////

package groupChat

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"gitlab.com/xx_network/primitives/id"
	"gitlab.com/xx_network/primitives/netTime"
	"reflect"
	"testing"
	"time"
)

// Unit test of newInternalMsg.
func Test_newInternalMsg(t *testing.T) {
	maxDataSize := 2 * internalMinLen
	im, err := newInternalMsg(maxDataSize)
	if err != nil {
		t.Errorf("newInternalMsg() returned an error: %+v", err)
	}

	if len(im.data) != maxDataSize {
		t.Errorf("newInternalMsg() set data to the wrong length."+
			"\nexpected: %d\nreceived: %d", maxDataSize, len(im.data))
	}
}

// Error path: the maxDataSize is smaller than the minimum size.
func Test_newInternalMsg_PayloadSizeError(t *testing.T) {
	maxDataSize := internalMinLen - 1
	expectedErr := fmt.Sprintf(newInternalSizeErr, maxDataSize, internalMinLen)

	_, err := newInternalMsg(maxDataSize)
	if err == nil || err.Error() != expectedErr {
		t.Errorf("newInternalMsg() failed to return the expected error."+
			"\nexpected: %s\nreceived: %+v", expectedErr, err)
	}
}

// Unit test of mapInternalMsg.
func Test_mapInternalMsg(t *testing.T) {
	// Create all the expected data
	timestamp := make([]byte, timestampLen)
	binary.LittleEndian.PutUint64(timestamp, uint64(netTime.Now().UnixNano()))
	senderID := id.NewIdFromString("test sender ID", id.User, t).Marshal()
	payload := []byte("Sample payload contents.")
	size := make([]byte, internalPayloadSizeLen)
	binary.LittleEndian.PutUint16(size, uint16(len(payload)))

	// Construct data into single slice
	data := bytes.NewBuffer(nil)
	data.Write(timestamp)
	data.Write(senderID)
	data.Write(size)
	data.Write(payload)

	// Map data
	im := mapInternalMsg(data.Bytes())

	// Check that the mapped values match the expected values
	if !bytes.Equal(timestamp, im.timestamp) {
		t.Errorf("mapInternalMsg() did not correctly map timestamp."+
			"\nexpected: %+v\nreceived: %+v", timestamp, im.timestamp)
	}

	if !bytes.Equal(senderID, im.senderID) {
		t.Errorf("mapInternalMsg() did not correctly map senderID."+
			"\nexpected: %+v\nreceived: %+v", senderID, im.senderID)
	}

	if !bytes.Equal(size, im.size) {
		t.Errorf("mapInternalMsg() did not correctly map size."+
			"\nexpected: %+v\nreceived: %+v", size, im.size)
	}

	if !bytes.Equal(payload, im.payload) {
		t.Errorf("mapInternalMsg() did not correctly map payload."+
			"\nexpected: %+v\nreceived: %+v", payload, im.payload)
	}
}

// Tests that a marshaled and unmarshalled internalMsg matches the original.
func TestInternalMsg_Marshal_unmarshalInternalMsg(t *testing.T) {
	im, _ := newInternalMsg(internalMinLen * 2)
	im.SetTimestamp(netTime.Now())
	im.SetSenderID(id.NewIdFromString("test sender ID", id.User, t))
	im.SetPayload([]byte("Sample payload message."))

	data := im.Marshal()

	newIm, err := unmarshalInternalMsg(data)
	if err != nil {
		t.Errorf("unmarshalInternalMsg() returned an error: %+v", err)
	}

	if !reflect.DeepEqual(im, newIm) {
		t.Errorf("unmarshalInternalMsg() did not return the expected internalMsg."+
			"\nexpected: %s\nreceived: %s", im, newIm)
	}
}

// Error path: error is returned when the data is too short.
func Test_unmarshalInternalMsg_DataLengthError(t *testing.T) {
	expectedErr := fmt.Sprintf(unmarshalInternalSizeErr, 0, internalMinLen)

	_, err := unmarshalInternalMsg(nil)
	if err == nil || err.Error() != expectedErr {
		t.Errorf("unmarshalInternalMsg() failed to return the expected error."+
			"\nexpected: %s\nreceived: %+v", expectedErr, err)
	}
}

// Happy path.
func TestInternalMsg_SetTimestamp_GetTimestamp(t *testing.T) {
	im, _ := newInternalMsg(internalMinLen * 2)
	timestamp := netTime.Now()
	im.SetTimestamp(timestamp)
	testTimestamp := im.GetTimestamp()

	if !timestamp.Equal(testTimestamp) {
		t.Errorf("Failed to get original timestamp."+
			"\nexpected: %s\nreceived: %s", timestamp, testTimestamp)
	}
}

// Happy path.
func TestInternalMsg_SetSenderID_GetSenderID(t *testing.T) {
	im, _ := newInternalMsg(internalMinLen * 2)
	sid := id.NewIdFromString("testSenderID", id.User, t)
	im.SetSenderID(sid)
	testID, err := im.GetSenderID()
	if err != nil {
		t.Errorf("GetSenderID() returned an error: %+v", err)
	}

	if !sid.Cmp(testID) {
		t.Errorf("Failed to get original sender ID."+
			"\nexpected: %s\nreceived: %s", sid, testID)
	}
}

// Tests that the original payload matches the saved one.
func TestInternalMsg_SetPayload_GetPayload(t *testing.T) {
	im, _ := newInternalMsg(internalMinLen * 2)
	payload := []byte("Test payload message.")
	im.SetPayload(payload)
	testPayload := im.GetPayload()

	if !bytes.Equal(payload, testPayload) {
		t.Errorf("Failed to get original sender payload."+
			"\nexpected: %s\nreceived: %s", payload, testPayload)
	}
}

// Happy path.
func TestInternalMsg_GetPayloadSize(t *testing.T) {
	im, _ := newInternalMsg(internalMinLen * 2)
	payload := []byte("Test payload message.")
	im.SetPayload(payload)

	if len(payload) != im.GetPayloadSize() {
		t.Errorf("GetPayloadSize() failed to return the correct size."+
			"\nexpected: %d\nreceived: %d", len(payload), im.GetPayloadSize())
	}
}

// Happy path.
func TestInternalMsg_GetPayloadMaxSize(t *testing.T) {
	im, _ := newInternalMsg(internalMinLen * 2)

	if internalMinLen != im.GetPayloadMaxSize() {
		t.Errorf("GetPayloadSize() failed to return the correct size."+
			"\nexpected: %d\nreceived: %d", internalMinLen, im.GetPayloadMaxSize())
	}
}

// Happy path.
func TestInternalMsg_String(t *testing.T) {
	im, _ := newInternalMsg(internalMinLen * 2)
	im.SetTimestamp(time.Date(1955, 11, 5, 12, 0, 0, 0, time.UTC))
	im.SetSenderID(id.NewIdFromString("test sender ID", id.User, t))
	payload := []byte("Sample payload message.")
	payload = append(payload, 0, 1, 2)
	im.SetPayload(payload)

	expected := "{timestamp:" + im.GetTimestamp().String() +
		", senderID:dGVzdCBzZW5kZXIgSUQAAAAAAAAAAAAAAAAAAAAAAAAD, " +
		"size:26, payload:\"Sample payload message.\\x00\\x01\\x02\"}"

	if im.String() != expected {
		t.Errorf("String() failed to return the expected value."+
			"\nexpected: %s\nreceived: %s", expected, im.String())
	}
}

// Happy path: tests that String returns the expected string for a nil
// internalMsg.
func TestInternalMsg_String_NilInternalMessage(t *testing.T) {
	im := internalMsg{}

	expected := "{timestamp:<nil>, senderID:<nil>, size:<nil>, payload:<nil>}"

	if im.String() != expected {
		t.Errorf("String() failed to return the expected value."+
			"\nexpected: %s\nreceived: %s", expected, im.String())
	}
}