///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC                                          //
//                                                                           //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file                                                              //
///////////////////////////////////////////////////////////////////////////////

package groupChat

import (
	gs "gitlab.com/elixxir/client/groupChat/groupStore"
	"gitlab.com/elixxir/client/storage/versioned"
	"gitlab.com/elixxir/crypto/group"
	"gitlab.com/elixxir/ekv"
	"gitlab.com/xx_network/primitives/id"
	"math/rand"
	"reflect"
	"strings"
	"testing"
	"time"
)

// Unit test of Manager.newManager.
func Test_newManager(t *testing.T) {
	kv := versioned.NewKV(make(ekv.Memstore))
	user := group.Member{
		ID:    id.NewIdFromString("userID", id.User, t),
		DhKey: randCycInt(rand.New(rand.NewSource(42))),
	}
	requestChan := make(chan gs.Group)
	requestFunc := func(g gs.Group) { requestChan <- g }
	receiveChan := make(chan MessageReceive)
	receiveFunc := func(msg MessageReceive) { receiveChan <- msg }
	m, err := newManager(nil, user.ID, user.DhKey, nil, nil, nil, nil, kv, requestFunc, receiveFunc)
	if err != nil {
		t.Errorf("newManager() returned an error: %+v", err)
	}

	if !m.gs.GetUser().Equal(user) {
		t.Errorf("newManager() failed to create a store with the correct user."+
			"\nexpected: %s\nreceived: %s", user, m.gs.GetUser())
	}

	if m.gs.Len() != 0 {
		t.Errorf("newManager() failed to create an empty store."+
			"\nexpected: %d\nreceived: %d", 0, m.gs.Len())
	}

	// Check if requestFunc works
	go m.requestFunc(gs.Group{})
	select {
	case <-requestChan:
	case <-time.NewTimer(5 * time.Millisecond).C:
		t.Errorf("Timed out waiting for requestFunc to be called.")
	}

	// Check if receiveFunc works
	go m.receiveFunc(MessageReceive{})
	select {
	case <-receiveChan:
	case <-time.NewTimer(5 * time.Millisecond).C:
		t.Errorf("Timed out waiting for receiveFunc to be called.")
	}
}

// Tests that Manager.newManager loads a group storage when it exists.
func Test_newManager_LoadStorage(t *testing.T) {
	prng := rand.New(rand.NewSource(42))
	kv := versioned.NewKV(make(ekv.Memstore))
	user := group.Member{
		ID:    id.NewIdFromString("userID", id.User, t),
		DhKey: randCycInt(rand.New(rand.NewSource(42))),
	}

	gStore, err := gs.NewStore(kv, user)
	if err != nil {
		t.Errorf("Failed to create new group storage: %+v", err)
	}

	for i := 0; i < 10; i++ {
		err := gStore.Add(newTestGroup(getGroup(), getGroup().NewInt(42), prng, t))
		if err != nil {
			t.Errorf("Failed to add group %d: %+v", i, err)
		}
	}

	m, err := newManager(nil, user.ID, user.DhKey, nil, nil, nil, nil, kv, nil, nil)
	if err != nil {
		t.Errorf("newManager() returned an error: %+v", err)
	}

	if !reflect.DeepEqual(gStore, m.gs) {
		t.Errorf("newManager() failed to load the expected storage."+
			"\nexpected: %+v\nreceived: %+v", gStore, m.gs)
	}
}

// Error path: an error is returned when a group cannot be loaded from storage.
func Test_newManager_LoadError(t *testing.T) {
	prng := rand.New(rand.NewSource(42))
	kv := versioned.NewKV(make(ekv.Memstore))
	user := group.Member{
		ID:    id.NewIdFromString("userID", id.User, t),
		DhKey: randCycInt(rand.New(rand.NewSource(42))),
	}

	gStore, err := gs.NewStore(kv, user)
	if err != nil {
		t.Errorf("Failed to create new group storage: %+v", err)
	}

	g := newTestGroup(getGroup(), getGroup().NewInt(42), prng, t)
	err = gStore.Add(g)
	if err != nil {
		t.Errorf("Failed to add group: %+v", err)
	}
	_ = kv.Prefix("GroupChatListStore").Delete("GroupChat/"+g.ID.String(), 0)

	expectedErr := strings.SplitN(newGroupStoreErr, "%", 2)[0]

	_, err = newManager(nil, user.ID, user.DhKey, nil, nil, nil, nil, kv, nil, nil)
	if err == nil || !strings.Contains(err.Error(), expectedErr) {
		t.Errorf("newManager() did not return the expected error."+
			"\nexpected: %s\nreceived: %+v", expectedErr, err)
	}
}

//
// func TestManager_StartProcesses(t *testing.T) {
// 	jww.SetLogThreshold(jww.LevelTrace)
// 	jww.SetStdoutThreshold(jww.LevelTrace)
// 	prng := rand.New(rand.NewSource(42))
// 	requestChan1 := make(chan gs.Group)
// 	requestFunc1 := func(g gs.Group) { requestChan1 <- g }
// 	receiveChan1 := make(chan MessageReceive)
// 	receiveFunc1 := func(msg MessageReceive) { receiveChan1 <- msg }
// 	requestChan2 := make(chan gs.Group)
// 	requestFunc2 := func(g gs.Group) { requestChan2 <- g }
// 	receiveChan2 := make(chan MessageReceive)
// 	receiveFunc2 := func(msg MessageReceive) { receiveChan2 <- msg }
// 	requestChan3 := make(chan gs.Group)
// 	requestFunc3 := func(g gs.Group) { requestChan3 <- g }
// 	receiveChan3 := make(chan MessageReceive)
// 	receiveFunc3 := func(msg MessageReceive) { receiveChan3 <- msg }
//
// 	m1, _ := newTestManagerWithStore(prng, 10, 0, requestFunc1, receiveFunc1, t)
// 	m2, _ := newTestManagerWithStore(prng, 10, 0, requestFunc2, receiveFunc2, t)
// 	m3, _ := newTestManagerWithStore(prng, 10, 0, requestFunc3, receiveFunc3, t)
//
// 	membership, err := group.NewMembership(m1.store.GetUser().GetContact(),
// 		m2.store.GetUser().GetContact(), m3.store.GetUser().GetContact())
// 	if err != nil {
// 		t.Errorf("Failed to generate new membership: %+v", err)
// 	}
//
// 	dhKeys := gs.GenerateDhKeyList(m1.gs.GetUser().ID,
// 		m1.store.GetUser().E2eDhPrivateKey, membership, m1.store.E2e().GetGroup())
//
// 	grp1 := newTestGroup(m1.store.E2e().GetGroup(), m1.store.GetUser().E2eDhPrivateKey, prng, t)
// 	grp1.Members = membership
// 	grp1.DhKeys = dhKeys
// 	grp1.ID = group.NewID(grp1.IdPreimage, grp1.Members)
// 	grp1.Key = group.NewKey(grp1.KeyPreimage, grp1.Members)
// 	grp2 := grp1.DeepCopy()
// 	grp2.DhKeys = gs.GenerateDhKeyList(m2.gs.GetUser().ID,
// 		m2.store.GetUser().E2eDhPrivateKey, membership, m2.store.E2e().GetGroup())
// 	grp3 := grp1.DeepCopy()
// 	grp3.DhKeys = gs.GenerateDhKeyList(m3.gs.GetUser().ID,
// 		m3.store.GetUser().E2eDhPrivateKey, membership, m3.store.E2e().GetGroup())
//
// 	err = m1.gs.Add(grp1)
// 	if err != nil {
// 		t.Errorf("Failed to add group to member 1: %+v", err)
// 	}
// 	err = m2.gs.Add(grp2)
// 	if err != nil {
// 		t.Errorf("Failed to add group to member 2: %+v", err)
// 	}
// 	err = m3.gs.Add(grp3)
// 	if err != nil {
// 		t.Errorf("Failed to add group to member 3: %+v", err)
// 	}
//
// 	_ = m1.StartProcesses()
// 	_ = m2.StartProcesses()
// 	_ = m3.StartProcesses()
//
// 	// Build request message
// 	requestMarshaled, err := proto.Marshal(&Request{
// 		Name:        grp1.Name,
// 		IdPreimage:  grp1.IdPreimage.Bytes(),
// 		KeyPreimage: grp1.KeyPreimage.Bytes(),
// 		Members:     grp1.Members.Serialize(),
// 		Message:     grp1.InitMessage,
// 	})
// 	if err != nil {
// 		t.Errorf("Failed to proto marshal message: %+v", err)
// 	}
// 	msg := message.Receive{
// 		Payload:     requestMarshaled,
// 		MessageType: message.GroupCreationRequest,
// 		Sender:      m1.gs.GetUser().ID,
// 	}
//
// 	m2.swb.(*switchboard.Switchboard).Speak(msg)
// 	m3.swb.(*switchboard.Switchboard).Speak(msg)
//
// 	select {
// 	case received := <-requestChan2:
// 		if !reflect.DeepEqual(grp2, received) {
// 			t.Errorf("Failed to receive expected group on requestChan."+
// 				"\nexpected: %#v\nreceived: %#v", grp2, received)
// 		}
// 	case <-time.NewTimer(5 * time.Millisecond).C:
// 		t.Error("Timed out waiting for request callback.")
// 	}
//
// 	select {
// 	case received := <-requestChan3:
// 		if !reflect.DeepEqual(grp3, received) {
// 			t.Errorf("Failed to receive expected group on requestChan."+
// 				"\nexpected: %#v\nreceived: %#v", grp3, received)
// 		}
// 	case <-time.NewTimer(5 * time.Millisecond).C:
// 		t.Error("Timed out waiting for request callback.")
// 	}
//
// 	contents := []byte("Test group message.")
// 	timestamp := netTime.Now()
//
// 	// Create cMix message and get public message
// 	cMixMsg, err := m1.newCmixMsg(grp1, contents, timestamp, m2.gs.GetUser(), prng)
// 	if err != nil {
// 		t.Errorf("Failed to create new cMix message: %+v", err)
// 	}
//
// 	internalMsg, _ := newInternalMsg(cMixMsg.ContentsSize() - publicMinLen)
// 	internalMsg.SetTimestamp(timestamp)
// 	internalMsg.SetSenderID(m1.gs.GetUser().ID)
// 	internalMsg.SetPayload(contents)
// 	expectedMsgID := group.NewMessageID(grp1.ID, internalMsg.Marshal())
//
// 	expectedMsg := MessageReceive{
// 		GroupID:        grp1.ID,
// 		ID:             expectedMsgID,
// 		Payload:        contents,
// 		SenderID:       m1.gs.GetUser().ID,
// 		RoundTimestamp: timestamp.Local(),
// 	}
//
// 	msg = message.Receive{
// 		Payload:        cMixMsg.Marshal(),
// 		MessageType:    message.Raw,
// 		Sender:         m1.gs.GetUser().ID,
// 		RoundTimestamp: timestamp.Local(),
// 	}
// 	m2.swb.(*switchboard.Switchboard).Speak(msg)
//
// 	select {
// 	case received := <-receiveChan2:
// 		if !reflect.DeepEqual(expectedMsg, received) {
// 			t.Errorf("Failed to receive expected group on receiveChan."+
// 				"\nexpected: %+v\nreceived: %+v", expectedMsg, received)
// 		}
// 	case <-time.NewTimer(5 * time.Millisecond).C:
// 		t.Error("Timed out waiting for receive callback.")
// 	}
// }

// Unit test of Manager.JoinGroup.
func TestManager_JoinGroup(t *testing.T) {
	prng := rand.New(rand.NewSource(42))
	m, _ := newTestManagerWithStore(prng, 10, 0, nil, nil, t)
	g := newTestGroup(m.store.E2e().GetGroup(), m.store.GetUser().E2eDhPrivateKey, prng, t)

	err := m.JoinGroup(g)
	if err != nil {
		t.Errorf("JoinGroup() returned an error: %+v", err)
	}

	if _, exists := m.gs.Get(g.ID); !exists {
		t.Errorf("JoinGroup() failed to add the group %s.", g.ID)
	}
}

// Error path: an error is returned when a group is joined twice.
func TestManager_JoinGroup_AddErr(t *testing.T) {
	prng := rand.New(rand.NewSource(42))
	m, g := newTestManagerWithStore(prng, 10, 0, nil, nil, t)
	expectedErr := strings.SplitN(joinGroupErr, "%", 2)[0]

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

// Unit test of Manager.LeaveGroup.
func TestManager_LeaveGroup(t *testing.T) {
	prng := rand.New(rand.NewSource(42))
	m, g := newTestManagerWithStore(prng, 10, 0, nil, nil, t)

	err := m.LeaveGroup(g.ID)
	if err != nil {
		t.Errorf("LeaveGroup() returned an error: %+v", err)
	}

	if _, exists := m.GetGroup(g.ID); exists {
		t.Error("LeaveGroup() failed to delete the group.")
	}
}

// Error path: an error is returned when no group with the ID exists
func TestManager_LeaveGroup_NoGroupError(t *testing.T) {
	prng := rand.New(rand.NewSource(42))
	m, _ := newTestManagerWithStore(prng, 10, 0, nil, nil, t)
	expectedErr := strings.SplitN(leaveGroupErr, "%", 2)[0]

	err := m.LeaveGroup(id.NewIdFromString("invalidID", id.Group, t))
	if err == nil || !strings.Contains(err.Error(), expectedErr) {
		t.Errorf("LeaveGroup() failed to return the expected error."+
			"\nexpected: %s\nreceived: %+v", expectedErr, err)
	}
}

// Unit test of Manager.GetGroups.
func TestManager_GetGroups(t *testing.T) {
	prng := rand.New(rand.NewSource(42))
	m, _ := newTestManagerWithStore(prng, 10, 0, nil, nil, t)

	list := m.GetGroups()
	for i, gid := range list {
		if err := m.gs.Remove(gid); err != nil {
			t.Errorf("Group %s does not exist (%d): %+v", gid, i, err)
		}
	}

	if m.gs.Len() != 0 {
		t.Errorf("GetGroups() returned %d IDs, which is %d less than is in "+
			"memory.", len(list), m.gs.Len())
	}
}

// Unit test of Manager.GetGroup.
func TestManager_GetGroup(t *testing.T) {
	prng := rand.New(rand.NewSource(42))
	m, g := newTestManagerWithStore(prng, 10, 0, nil, nil, t)

	testGrp, exists := m.GetGroup(g.ID)
	if !exists {
		t.Error("GetGroup() failed to find a group that should exist.")
	}

	if !reflect.DeepEqual(g, testGrp) {
		t.Errorf("GetGroup() failed to return the expected group."+
			"\nexpected: %#v\nreceived: %#v", g, testGrp)
	}

	testGrp, exists = m.GetGroup(id.NewIdFromString("invalidID", id.Group, t))
	if exists {
		t.Errorf("GetGroup() returned a group that should not exist: %#v", testGrp)
	}
}

// Unit test of Manager.NumGroups. First a manager is created with 10 groups
// and the initial number is checked. Then the number of groups is checked after
// leaving each until the number left is 0.
func TestManager_NumGroups(t *testing.T) {
	expectedNum := 10
	m, _ := newTestManagerWithStore(rand.New(rand.NewSource(42)), expectedNum,
		0, nil, nil, t)

	groups := append([]*id.ID{{}}, m.GetGroups()...)

	for i, gid := range groups {
		_ = m.LeaveGroup(gid)

		if m.NumGroups() != expectedNum-i {
			t.Errorf("NumGroups() failed to return the expected number of "+
				"groups (%d).\nexpected: %d\nreceived: %d",
				i, expectedNum-i, m.NumGroups())
		}
	}

}