package protocol

import (
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/suite"

	"github.com/status-im/status-go/eth-node/types"
	"github.com/status-im/status-go/protocol/common"
	"github.com/status-im/status-go/protocol/protobuf"
)

func TestActivityCenterPersistence(t *testing.T) {
	suite.Run(t, new(ActivityCenterPersistenceTestSuite))
}

type ActivityCenterPersistenceTestSuite struct {
	suite.Suite
	idCounter int
}

func (s *ActivityCenterPersistenceTestSuite) SetupTest() {
	s.idCounter = 0
}

func currentMilliseconds() uint64 {
	c := time.Now().UnixMilli()
	return uint64(c)
}

func (s *ActivityCenterPersistenceTestSuite) createNotifications(p *sqlitePersistence, notifications []*ActivityCenterNotification) []*ActivityCenterNotification {
	now := currentMilliseconds()
	for index, notif := range notifications {
		if notif.Timestamp == 0 {
			notif.Timestamp = now
		}
		if len(notif.ID) == 0 {
			s.idCounter++
			notif.ID = types.HexBytes(strconv.Itoa(s.idCounter + index))
		}
		if notif.UpdatedAt == 0 {
			notif.UpdatedAt = now
		}
		_, err := p.SaveActivityCenterNotification(notif, true)
		s.Require().NoError(err, notif.ID)
	}

	// Fetches notifications to get an up-to-date slice.
	var createdNotifications []*ActivityCenterNotification
	for _, notif := range notifications {
		n, err := p.GetActivityCenterNotificationByID(notif.ID)
		s.Require().NoError(err, notif.ID)
		createdNotifications = append(createdNotifications, n)
	}

	return createdNotifications
}

func (s *ActivityCenterPersistenceTestSuite) Test_DeleteActivityCenterNotificationsWhenEmpty() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	chat := CreatePublicChat("test-chat", &testTimeSource{})
	message := common.NewMessage()
	message.Text = "sample text"
	chat.LastMessage = message
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	s.createNotifications(p, []*ActivityCenterNotification{
		{
			Type: ActivityCenterNotificationTypeMention,
		},
	})

	var count uint64
	count, _ = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
	s.Require().Equal(uint64(1), count)

	_, err = p.MarkActivityCenterNotificationsDeleted([]types.HexBytes{}, currentMilliseconds())
	s.Require().NoError(err)

	count, _ = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
	s.Require().Equal(uint64(1), count)
}

func (s *ActivityCenterPersistenceTestSuite) Test_DeleteActivityCenterNotificationsWithMultipleIds() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	chat := CreatePublicChat("test-chat", &testTimeSource{})
	message := common.NewMessage()
	message.Text = "sample text"
	chat.LastMessage = message
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	notifications := s.createNotifications(p, []*ActivityCenterNotification{
		{Type: ActivityCenterNotificationTypeMention},
		{Type: ActivityCenterNotificationTypeNewOneToOne},
		{Type: ActivityCenterNotificationTypeNewOneToOne},
	})

	var count uint64
	count, _ = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
	s.Require().Equal(uint64(3), count)

	_, err = p.MarkActivityCenterNotificationsDeleted([]types.HexBytes{notifications[1].ID, notifications[2].ID}, currentMilliseconds())
	s.Require().NoError(err)

	count, _ = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
	s.Require().Equal(uint64(1), count)
}

func (s *ActivityCenterPersistenceTestSuite) Test_DeleteActivityCenterNotificationsForMessage() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	chat := CreatePublicChat("test-chat", &testTimeSource{})
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	chat2 := CreatePublicChat("test-chat", &testTimeSource{})
	err = p.SaveChat(*chat2)
	s.Require().NoError(err)

	messages := []*common.Message{
		{
			ID:          "0x1",
			ChatMessage: &protobuf.ChatMessage{},
			LocalChatID: chat.ID,
		},
		{
			ID:          "0x2",
			ChatMessage: &protobuf.ChatMessage{},
			LocalChatID: chat.ID,
		},
		{
			ChatMessage: &protobuf.ChatMessage{},
			ID:          "0x3",
		},
	}
	err = p.SaveMessages(messages)
	s.Require().NoError(err)

	chat.LastMessage = messages[1]
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	chatMessages, _, err := p.MessageByChatID(chat.ID, "", 2)
	s.Require().NoError(err)
	s.Require().Len(chatMessages, 2)

	nID1 := types.HexBytes("1")
	nID2 := types.HexBytes("2")
	nID3 := types.HexBytes("3")
	nID4 := types.HexBytes("4")

	s.createNotifications(p, []*ActivityCenterNotification{
		{
			ID:      nID1,
			ChatID:  chat.ID,
			Type:    ActivityCenterNotificationTypeMention,
			Message: messages[0],
		},
		{
			ID:      nID2,
			ChatID:  chat.ID,
			Type:    ActivityCenterNotificationTypeMention,
			Message: messages[1],
		},
		{
			ID:     nID3,
			ChatID: chat.ID,
			Type:   ActivityCenterNotificationTypeMention,
		},
		{
			ID:   nID4,
			Type: ActivityCenterNotificationTypeMention,
		},
	})

	// Test: soft delete only the notifications that have Message.ID == messages[0].ID.
	_, err = p.DeleteActivityCenterNotificationForMessage(chat.ID, messages[0].ID, currentMilliseconds())
	s.Require().NoError(err)

	notif, err := p.GetActivityCenterNotificationByID(nID1)
	s.Require().NoError(err)
	s.Require().NotNil(notif)
	s.Require().True(notif.Deleted)
	s.Require().True(notif.Dismissed)
	s.Require().True(notif.Read)

	// Other notifications are not affected.
	for _, id := range []types.HexBytes{nID2, nID3, nID4} {
		notif, err = p.GetActivityCenterNotificationByID(id)
		s.Require().NoError(err)
		s.Require().NotNil(notif)
		s.Require().False(notif.Deleted, notif.ID)
		s.Require().False(notif.Dismissed, notif.ID)
		s.Require().False(notif.Read, notif.ID)
	}

	// Test: soft delete the notifications that have Message.ID == messages[1].ID
	_, err = p.DeleteActivityCenterNotificationForMessage(chat.ID, messages[1].ID, currentMilliseconds())
	s.Require().NoError(err)

	notif, err = p.GetActivityCenterNotificationByID(nID2)
	s.Require().NoError(err)
	s.Require().NotNil(notif)
	s.Require().True(notif.Deleted, notif.ID)
	s.Require().True(notif.Dismissed, notif.ID)
	s.Require().True(notif.Read, notif.ID)

	// Check that notifications with LastMessage.ID == messages[1].ID will remain.
	notif, err = p.GetActivityCenterNotificationByID(nID3)
	s.Require().NoError(err)
	s.Require().NotNil(notif)
	s.Require().False(notif.Deleted, notif.ID)
	s.Require().False(notif.Dismissed, notif.ID)
	s.Require().False(notif.Read, notif.ID)

	notif, err = p.GetActivityCenterNotificationByID(nID4)
	s.Require().NoError(err)
	s.Require().NotNil(notif)
	s.Require().False(notif.Deleted)
	s.Require().False(notif.Dismissed)
	s.Require().False(notif.Read)

	// Test: don't do anything if passed a chat and message without notifications.
	_, err = p.DeleteActivityCenterNotificationForMessage(chat2.ID, messages[2].ID, currentMilliseconds())
	s.Require().NoError(err)
}

func (s *ActivityCenterPersistenceTestSuite) Test_DeleteActivityCenterNotificationsForMessage_LastMessage() {
	// Create the temporary test-database that will be used to store chats, messages, and notifications
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	// Create and save the public chat that will be used to group our test messages
	chat := CreatePublicChat("test-chat", &testTimeSource{})
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	// Define multiple test messages for our chat so we can emulate a chat with a latest message.
	messages := []*common.Message{
		{
			ID:          "0x1",
			ChatMessage: &protobuf.ChatMessage{},
			LocalChatID: chat.ID,
		},
		{
			ID:          "0x2",
			ChatMessage: &protobuf.ChatMessage{},
			LocalChatID: chat.ID,
		},
	}
	err = p.SaveMessages(messages)
	s.Require().NoError(err)

	chat.LastMessage = messages[1]
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	chatMessages, _, err := p.MessageByChatID(chat.ID, "", 2)
	s.Require().NoError(err)
	s.Require().Len(chatMessages, 2)

	// Define multiple notifications of different types to emulate the removal of notifications when deleting a message
	nID1 := types.HexBytes("1")
	nID2 := types.HexBytes("2")
	nID3 := types.HexBytes("3")
	nID4 := types.HexBytes("4")

	s.createNotifications(p, []*ActivityCenterNotification{
		{
			ID:          nID1,
			ChatID:      chat.ID,
			Type:        ActivityCenterNotificationTypeMention,
			Message:     messages[0],
			LastMessage: messages[1],
		},
		{
			ID:      nID2,
			ChatID:  chat.ID,
			Type:    ActivityCenterNotificationTypeMention,
			Message: messages[1],
		},
		{
			ID:          nID3,
			ChatID:      chat.ID,
			Type:        ActivityCenterNotificationTypeNewOneToOne,
			LastMessage: messages[1],
		},
		{
			ID:          nID4,
			ChatID:      chat.ID,
			Type:        ActivityCenterNotificationTypeNewPrivateGroupChat,
			LastMessage: messages[1],
		},
	})

	// Action: soft delete notifications related to a chat and message
	_, err = p.DeleteActivityCenterNotificationForMessage(chat.ID, messages[1].ID, currentMilliseconds())
	s.Require().NoError(err)

	// Test: check that notifications unrelated to the message are not affected.
	notif, err := p.GetActivityCenterNotificationByID(nID1)
	s.Require().NoError(err)
	s.Require().NotNil(notif)
	s.Require().False(notif.Deleted, notif.ID)
	s.Require().False(notif.Dismissed, notif.ID)
	s.Require().False(notif.Read, notif.ID)

	// Test: check notifications directly related to the message are soft deleted
	notif, err = p.GetActivityCenterNotificationByID(nID2)
	s.Require().NoError(err)
	s.Require().NotNil(notif)
	s.Require().True(notif.Deleted)
	s.Require().True(notif.Dismissed)
	s.Require().True(notif.Read)

	// Test: check NewOneToOne or NewPrivateGroupChat notifications that are indirectly related to the message are soft deleted
	for _, id := range []types.HexBytes{nID3, nID4} {
		notif, err = p.GetActivityCenterNotificationByID(id)
		s.Require().NoError(err)
		s.Require().NotNil(notif)
		s.Require().False(notif.Deleted)
		s.Require().False(notif.Dismissed)
		s.Require().False(notif.Read)
	}
}

func (s *ActivityCenterPersistenceTestSuite) Test_AcceptActivityCenterNotificationsForInvitesFromUser() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	nID1 := types.HexBytes("1")
	nID2 := types.HexBytes("2")
	nID3 := types.HexBytes("3")
	nID4 := types.HexBytes("4")

	userPublicKey := "zQ3sh"

	notifications := []*ActivityCenterNotification{
		{
			ID:        nID1,
			Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
			Timestamp: 1,
		},
		{
			ID:        nID2,
			Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
			Timestamp: 1,
			Author:    userPublicKey,
		},
		{
			ID:        nID3,
			Type:      ActivityCenterNotificationTypeMention,
			Timestamp: 1,
			Author:    userPublicKey,
		},
		{
			ID:        nID4,
			Timestamp: 1,
			Type:      ActivityCenterNotificationTypeMention,
		},
	}

	var notif *ActivityCenterNotification
	for _, notif = range notifications {
		_, err = p.SaveActivityCenterNotification(notif, true)
		s.Require().NoError(err, notif.ID)
	}

	// Only notifications of type new private group chat and with Author equal to
	// userPublicKey should be marked as accepted & read.
	_, err = p.GetActivityCenterNotificationByID(nID2)
	s.Require().NoError(err)
	s.Require().False(notifications[0].Accepted)
	s.Require().False(notifications[0].Read)

	notifications, err = p.AcceptActivityCenterNotificationsForInvitesFromUser(userPublicKey, 1)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	s.Require().Equal(nID2, notifications[0].ID)

	notif, err = p.GetActivityCenterNotificationByID(nID2)
	s.Require().NoError(err)
	s.Require().True(notif.Accepted)
	s.Require().True(notif.Read)

	// Deleted notifications are ignored.
	notif = &ActivityCenterNotification{
		ID:        types.HexBytes("99"),
		Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
		Timestamp: 1,
		Author:    userPublicKey,
		Deleted:   true,
	}
	_, err = p.SaveActivityCenterNotification(notif, true)
	s.Require().NoError(err)
	_, err = p.AcceptActivityCenterNotificationsForInvitesFromUser(userPublicKey, currentMilliseconds())
	s.Require().NoError(err)
	notif, err = p.GetActivityCenterNotificationByID(notif.ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().False(notif.Read)
	s.Require().True(notif.Deleted)

	// Dismissed notifications are ignored.
	notif = &ActivityCenterNotification{
		ID:        types.HexBytes("100"),
		Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
		Timestamp: 1,
		Author:    userPublicKey,
		Dismissed: true,
	}
	_, err = p.SaveActivityCenterNotification(notif, true)
	s.Require().NoError(err)
	_, err = p.AcceptActivityCenterNotificationsForInvitesFromUser(userPublicKey, currentMilliseconds())
	s.Require().NoError(err)
	notif, err = p.GetActivityCenterNotificationByID(notif.ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().False(notif.Read)
	s.Require().True(notif.Dismissed)
}

func (s *ActivityCenterPersistenceTestSuite) Test_GetToProcessActivityCenterNotificationIds() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	notifications := s.createNotifications(p, []*ActivityCenterNotification{
		{
			Type:    ActivityCenterNotificationTypeNewPrivateGroupChat,
			Deleted: true,
		},
		{
			Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
			Dismissed: true,
		},
		{
			Type:     ActivityCenterNotificationTypeMention,
			Accepted: true,
		},
		{
			Type: ActivityCenterNotificationTypeMention,
		},
	})

	ids, err := p.GetToProcessActivityCenterNotificationIds()
	s.Require().NoError(err)
	s.Require().Len(ids, 1)
	s.Require().Equal(notifications[3].ID, types.HexBytes(ids[0]))
}

func (s *ActivityCenterPersistenceTestSuite) Test_HasPendingNotificationsForChat() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	chat := CreatePublicChat("test-chat", &testTimeSource{})
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	// Test: there are no notifications.
	result, err := p.HasPendingNotificationsForChat(chat.ID)
	s.Require().NoError(err)
	s.Require().False(result)

	// Test: there are only deleted, dismissed or accepted notifications,
	// therefore, no pending notifications.
	s.createNotifications(p, []*ActivityCenterNotification{
		{
			ChatID:  chat.ID,
			Type:    ActivityCenterNotificationTypeNewPrivateGroupChat,
			Deleted: true,
		},
		{
			ChatID:    chat.ID,
			Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
			Dismissed: true,
		},
		{
			ChatID:   chat.ID,
			Type:     ActivityCenterNotificationTypeMention,
			Accepted: true,
		},
	})

	result, err = p.HasPendingNotificationsForChat(chat.ID)
	s.Require().NoError(err)
	s.Require().False(result)

	// Test: there's one pending notification.
	notif := &ActivityCenterNotification{
		ID:        types.HexBytes("99"),
		ChatID:    chat.ID,
		Type:      ActivityCenterNotificationTypeCommunityRequest,
		Timestamp: 1,
	}
	_, err = p.SaveActivityCenterNotification(notif, true)
	s.Require().NoError(err)

	result, err = p.HasPendingNotificationsForChat(chat.ID)
	s.Require().NoError(err)
	s.Require().True(result)
}

func (s *ActivityCenterPersistenceTestSuite) Test_DismissAllActivityCenterNotificationsFromUser() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	publicKey := "0x04"

	notifications := s.createNotifications(p, []*ActivityCenterNotification{
		{
			Type:    ActivityCenterNotificationTypeNewPrivateGroupChat,
			Author:  publicKey,
			Deleted: true,
		},
		{
			Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
			Author:    publicKey,
			Dismissed: true,
		},
		{
			Type:     ActivityCenterNotificationTypeMention,
			Author:   publicKey,
			Accepted: true,
		},
		{
			Type:   ActivityCenterNotificationTypeMention,
			Author: "0x09",
		},
		{
			Type:   ActivityCenterNotificationTypeMention,
			Author: publicKey,
		},
	})

	_, err = p.DismissAllActivityCenterNotificationsFromUser(publicKey, 1)
	s.Require().NoError(err)

	// Ignores already soft deleted.
	notif, err := p.GetActivityCenterNotificationByID(notifications[0].ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().False(notif.Read)
	s.Require().False(notif.Dismissed)
	s.Require().True(notif.Deleted)

	// Ignores already dismissed.
	notif, err = p.GetActivityCenterNotificationByID(notifications[1].ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().False(notif.Read)
	s.Require().True(notif.Dismissed)
	s.Require().False(notif.Deleted)

	// Ignores already accepted.
	notif, err = p.GetActivityCenterNotificationByID(notifications[2].ID)
	s.Require().NoError(err)
	s.Require().True(notif.Accepted)
	s.Require().False(notif.Read)
	s.Require().False(notif.Dismissed)
	s.Require().False(notif.Deleted)

	// Ignores notification from different author.
	notif, err = p.GetActivityCenterNotificationByID(notifications[3].ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().False(notif.Read)
	s.Require().False(notif.Dismissed)
	s.Require().False(notif.Deleted)

	// Finally, dismiss and mark as read this one notification.
	notif, err = p.GetActivityCenterNotificationByID(notifications[4].ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().True(notif.Read)
	s.Require().True(notif.Dismissed)
	s.Require().False(notif.Deleted)
}

func (s *ActivityCenterPersistenceTestSuite) Test_DismissAllActivityCenterNotificationsFromChatID() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	chatID := "0x99"

	notifications := s.createNotifications(p, []*ActivityCenterNotification{
		{
			ChatID:  chatID,
			Type:    ActivityCenterNotificationTypeNewPrivateGroupChat,
			Deleted: true,
		},
		{
			ChatID:    chatID,
			Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
			Dismissed: true,
		},
		{
			ChatID:   chatID,
			Type:     ActivityCenterNotificationTypeMention,
			Accepted: true,
		},
		{
			Type: ActivityCenterNotificationTypeMention,
		},
		{
			ChatID: chatID,
			Type:   ActivityCenterNotificationTypeContactRequest,
		},
		{
			ChatID: chatID,
			Type:   ActivityCenterNotificationTypeMention,
		},
	})

	_, err = p.DismissAllActivityCenterNotificationsFromChatID(chatID, currentMilliseconds())
	s.Require().NoError(err)

	// Ignores already soft deleted.
	notif, err := p.GetActivityCenterNotificationByID(notifications[0].ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().False(notif.Read)
	s.Require().False(notif.Dismissed)
	s.Require().True(notif.Deleted)

	// Do not ignore already dismissed, because notifications can become
	// read/unread AND dismissed, and the method should still update the Read
	// column.
	notif, err = p.GetActivityCenterNotificationByID(notifications[1].ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().True(notif.Read)
	s.Require().True(notif.Dismissed)
	s.Require().False(notif.Deleted)

	// Ignores already accepted.
	notif, err = p.GetActivityCenterNotificationByID(notifications[2].ID)
	s.Require().NoError(err)
	s.Require().True(notif.Accepted)
	s.Require().False(notif.Read)
	s.Require().False(notif.Dismissed)
	s.Require().False(notif.Deleted)

	// Ignores notification from different chat.
	notif, err = p.GetActivityCenterNotificationByID(notifications[3].ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().False(notif.Read)
	s.Require().False(notif.Dismissed)
	s.Require().False(notif.Deleted)

	// Ignores contact request notifications.
	notif, err = p.GetActivityCenterNotificationByID(notifications[4].ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().False(notif.Read)
	s.Require().False(notif.Dismissed)
	s.Require().False(notif.Deleted)

	// Finally, dismiss and mark as read this one notification.
	notif, err = p.GetActivityCenterNotificationByID(notifications[5].ID)
	s.Require().NoError(err)
	s.Require().False(notif.Accepted)
	s.Require().True(notif.Read)
	s.Require().True(notif.Dismissed)
	s.Require().False(notif.Deleted)
}

func (s *ActivityCenterPersistenceTestSuite) Test_ActiveContactRequestNotification() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	chat := CreatePublicChat("test-chat", &testTimeSource{})
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	contactID := "0x99"

	// Test: ignores deleted/dismissed/accepted notifications, as well as
	// notifications not associated to any chat.
	s.createNotifications(p, []*ActivityCenterNotification{
		{
			ChatID:  chat.ID,
			Author:  contactID,
			Type:    ActivityCenterNotificationTypeContactRequest,
			Deleted: true,
		},
		{
			ChatID:    chat.ID,
			Author:    contactID,
			Type:      ActivityCenterNotificationTypeContactRequest,
			Dismissed: true,
		},
		{
			ChatID:   chat.ID,
			Author:   contactID,
			Type:     ActivityCenterNotificationTypeContactRequest,
			Accepted: true,
		},
	})

	notif, err := p.ActiveContactRequestNotification(contactID)
	s.Require().NoError(err)
	s.Require().Nil(notif)

	// Test: Ignores notifications that are not contact requests.
	s.createNotifications(p, []*ActivityCenterNotification{
		{ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeCommunityInvitation},
		{ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeCommunityKicked},
		{ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeCommunityMembershipRequest},
		{ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeCommunityRequest},
		{ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeContactVerification},
		{ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeMention},
		{ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeNewOneToOne},
		{ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeNewPrivateGroupChat},
		{ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeReply},
	})

	notif, err = p.ActiveContactRequestNotification(contactID)
	s.Require().NoError(err)
	s.Require().Nil(notif)

	// Test: Returns one, and only one contact request notification for the
	// contact under test.
	s.createNotifications(p, []*ActivityCenterNotification{
		{ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeContactRequest},
	})

	notif, err = p.ActiveContactRequestNotification(contactID)
	s.Require().NoError(err)
	s.Require().NotNil(notif)
	s.Require().Equal(ActivityCenterNotificationTypeContactRequest, notif.Type)

	// Test: In case there's more than one notification, return the most recent
	// one according to the notification's timestamp.
	expectedID := types.HexBytes("667")

	t1 := currentMilliseconds()
	t2 := currentMilliseconds()
	s.createNotifications(p, []*ActivityCenterNotification{
		{
			ID:        expectedID,
			Timestamp: t2 + 1,
			ChatID:    chat.ID,
			Author:    contactID,
			Type:      ActivityCenterNotificationTypeContactRequest,
		},
		{
			ID:        types.HexBytes("666"),
			Timestamp: t1,
			ChatID:    chat.ID,
			Author:    contactID,
			Type:      ActivityCenterNotificationTypeContactRequest,
		},
	})

	notif, err = p.ActiveContactRequestNotification(contactID)
	s.Require().NoError(err)
	s.Require().Equal(expectedID, notif.ID)
}

func (s *ActivityCenterPersistenceTestSuite) Test_UnreadActivityCenterNotificationsCount() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	s.createNotifications(p, []*ActivityCenterNotification{
		{Type: ActivityCenterNotificationTypeMention, Read: true},
		{Type: ActivityCenterNotificationTypeNewOneToOne, Deleted: true},
		{Type: ActivityCenterNotificationTypeMention, Dismissed: true},
		{Type: ActivityCenterNotificationTypeCommunityRequest, Accepted: true},
		{Type: ActivityCenterNotificationTypeContactRequest},
	})

	// Test: Ignore soft deleted and accepted.
	count, err := p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, true)
	s.Require().NoError(err)
	s.Require().Equal(uint64(3), count)
}

func (s *ActivityCenterPersistenceTestSuite) Test_UnreadAndAcceptedActivityCenterNotificationsCount() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	s.createNotifications(p, []*ActivityCenterNotification{
		{Type: ActivityCenterNotificationTypeMention, Read: true},
		{Type: ActivityCenterNotificationTypeNewOneToOne, Deleted: true},
		{Type: ActivityCenterNotificationTypeMention, Dismissed: true},
		{Type: ActivityCenterNotificationTypeCommunityRequest, Accepted: true},
		{Type: ActivityCenterNotificationTypeContactRequest},
	})

	// Test: counts everything, except soft deleted notifications.
	count, err := p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, true)
	s.Require().NoError(err)
	s.Require().Equal(uint64(3), count)

	// Test: counts everything, except soft deleted ones and limit by type.
	count, err = p.ActivityCenterNotificationsCount([]ActivityCenterType{
		ActivityCenterNotificationTypeContactRequest,
	}, ActivityCenterQueryParamsReadUnread, true)
	s.Require().NoError(err)
	s.Require().Equal(uint64(1), count)
}

func (s *ActivityCenterPersistenceTestSuite) Test_ActivityCenterPersistence() {
	nID1 := types.HexBytes([]byte("1"))
	nID2 := types.HexBytes([]byte("2"))
	nID3 := types.HexBytes([]byte("3"))
	nID4 := types.HexBytes([]byte("4"))

	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	chat := CreatePublicChat("test-chat", &testTimeSource{})
	message := common.NewMessage()
	message.Text = "sample text"
	chat.LastMessage = message
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	notification := &ActivityCenterNotification{
		ID:        nID1,
		Type:      ActivityCenterNotificationTypeNewOneToOne,
		ChatID:    chat.ID,
		Timestamp: 1,
	}
	_, err = p.SaveActivityCenterNotification(notification, true)
	s.Require().NoError(err)

	cursor, notifications, err := p.ActivityCenterNotifications("", 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
	s.Require().NoError(err)
	s.Require().Empty(cursor)
	s.Require().Len(notifications, 1)
	s.Require().Equal(chat.ID, notifications[0].ChatID)
	s.Require().Equal(message, notifications[0].LastMessage)

	// Add another notification

	notification = &ActivityCenterNotification{
		ID:        nID2,
		Type:      ActivityCenterNotificationTypeNewOneToOne,
		Timestamp: 2,
	}
	_, err = p.SaveActivityCenterNotification(notification, true)
	s.Require().NoError(err)

	cursor, notifications, err = p.ActivityCenterNotifications("", 1, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	s.Require().NotEmpty(cursor)
	s.Require().Equal(nID2, notifications[0].ID)

	// fetch next pagination

	cursor, notifications, err = p.ActivityCenterNotifications(cursor, 1, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	s.Require().Empty(cursor)
	s.Require().False(notifications[0].Read)
	s.Require().Equal(nID1, notifications[0].ID)

	// Check count
	count, err := p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
	s.Require().NoError(err)
	s.Require().Equal(uint64(2), count)

	var updatedAt uint64 = 1
	// Mark first one as read
	s.Require().NoError(p.MarkActivityCenterNotificationsRead([]types.HexBytes{nID1}, updatedAt))
	count, err = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
	s.Require().NoError(err)
	s.Require().Equal(uint64(1), count)

	// Mark first one as unread
	updatedAt++
	_, err = p.MarkActivityCenterNotificationsUnread([]types.HexBytes{nID1}, updatedAt)
	s.Require().NoError(err)
	count, err = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
	s.Require().NoError(err)
	s.Require().Equal(uint64(2), count)

	// Mark all read
	updatedAt++
	s.Require().NoError(p.MarkAllActivityCenterNotificationsRead(updatedAt))
	_, notifications, err = p.ActivityCenterNotifications(cursor, 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
	s.Require().NoError(err)
	s.Require().Len(notifications, 2)
	s.Require().Empty(cursor)
	s.Require().True(notifications[0].Read)
	s.Require().True(notifications[1].Read)

	// Check count
	count, err = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
	s.Require().NoError(err)
	s.Require().Equal(uint64(0), count)

	// Mark first one as accepted
	updatedAt++
	notifications, err = p.AcceptActivityCenterNotifications([]types.HexBytes{nID1}, updatedAt)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	_, notifications, err = p.ActivityCenterNotifications("", 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
	s.Require().NoError(err)
	// It should not be returned anymore
	s.Require().Len(notifications, 1)

	// Mark last one as dismissed
	updatedAt++
	s.Require().NoError(p.DismissActivityCenterNotifications([]types.HexBytes{nID2}, updatedAt))
	_, notifications, err = p.ActivityCenterNotifications("", 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
	s.Require().NoError(err)

	s.Require().Len(notifications, 1)
	s.Require().True(notifications[0].Dismissed)

	// Insert new notification
	notification = &ActivityCenterNotification{
		ID:        nID3,
		Type:      ActivityCenterNotificationTypeNewOneToOne,
		Timestamp: 3,
	}
	_, err = p.SaveActivityCenterNotification(notification, true)
	s.Require().NoError(err)

	// Mark all as accepted
	updatedAt++
	notifications, err = p.AcceptAllActivityCenterNotifications(updatedAt)
	s.Require().NoError(err)
	s.Require().Len(notifications, 2)

	_, notifications, err = p.ActivityCenterNotifications("", 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
	s.Require().NoError(err)

	s.Require().Len(notifications, 1)

	// Insert new notification
	notification = &ActivityCenterNotification{
		ID:        nID4,
		Type:      ActivityCenterNotificationTypeNewOneToOne,
		Timestamp: 4,
	}
	_, err = p.SaveActivityCenterNotification(notification, true)
	s.Require().NoError(err)

	// Mark all as dismissed
	updatedAt++
	s.Require().NoError(p.DismissAllActivityCenterNotifications(updatedAt))
	_, notifications, err = p.ActivityCenterNotifications("", 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
	s.Require().NoError(err)

	s.Require().Len(notifications, 2)
	s.Require().True(notifications[0].Dismissed)
	s.Require().True(notifications[1].Dismissed)
}

func (s *ActivityCenterPersistenceTestSuite) Test_ActivityCenterReadUnreadPagination() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	initialOrFinalCursor := ""

	chat := CreatePublicChat("test-chat", &testTimeSource{})
	message := common.NewMessage()
	message.Text = "sample text"
	chat.LastMessage = message
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	nID1 := types.HexBytes("1")
	nID2 := types.HexBytes("2")
	nID3 := types.HexBytes("3")
	nID4 := types.HexBytes("4")
	nID5 := types.HexBytes("5")

	allNotifications := []*ActivityCenterNotification{
		{
			ID:        nID1,
			Type:      ActivityCenterNotificationTypeNewOneToOne,
			ChatID:    chat.ID,
			Timestamp: 1,
		},
		{
			ID:        nID2,
			Type:      ActivityCenterNotificationTypeNewOneToOne,
			ChatID:    chat.ID,
			Timestamp: 1,
		},
		{
			ID:        nID3,
			Type:      ActivityCenterNotificationTypeNewOneToOne,
			ChatID:    chat.ID,
			Timestamp: 1,
		},
		{
			ID:        nID4,
			Type:      ActivityCenterNotificationTypeNewOneToOne,
			ChatID:    chat.ID,
			Timestamp: 1,
		},
		{
			ID:        nID5,
			Type:      ActivityCenterNotificationTypeNewOneToOne,
			ChatID:    chat.ID,
			Timestamp: 1,
		},
	}

	for _, notification := range allNotifications {
		_, err = p.SaveActivityCenterNotification(notification, true)
		s.Require().NoError(err)
	}

	// Mark the notification as read
	err = p.MarkActivityCenterNotificationsRead([]types.HexBytes{nID2}, currentMilliseconds())
	s.Require().NoError(err)
	err = p.MarkActivityCenterNotificationsRead([]types.HexBytes{nID4}, currentMilliseconds())
	s.Require().NoError(err)

	// Fetch UNREAD notifications, first page.
	cursor, notifications, err := p.ActivityCenterNotifications(
		initialOrFinalCursor,
		1,
		[]ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
		ActivityCenterQueryParamsReadUnread,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	s.Require().Equal(nID5, notifications[0].ID)
	s.Require().NotEmpty(cursor)

	// Fetch next pages.
	cursor, notifications, err = p.ActivityCenterNotifications(
		cursor,
		1,
		[]ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
		ActivityCenterQueryParamsReadUnread,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	s.Require().Equal(nID3, notifications[0].ID)
	s.Require().NotEmpty(cursor)

	cursor, notifications, err = p.ActivityCenterNotifications(
		cursor,
		1,
		[]ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
		ActivityCenterQueryParamsReadUnread,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	s.Require().Equal(nID1, notifications[0].ID)
	s.Require().Empty(cursor)

	// Fetch READ notifications, first page.
	cursor, notifications, err = p.ActivityCenterNotifications(
		initialOrFinalCursor,
		1,
		[]ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
		ActivityCenterQueryParamsReadRead,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	s.Require().Equal(nID4, notifications[0].ID)
	s.Require().NotEmpty(cursor)

	// Fetch next page.
	cursor, notifications, err = p.ActivityCenterNotifications(
		cursor,
		1,
		[]ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
		ActivityCenterQueryParamsReadRead,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	s.Require().Equal(nID2, notifications[0].ID)
	s.Require().Empty(cursor)
}

func (s *ActivityCenterPersistenceTestSuite) Test_ActivityCenterReadUnreadFilterByTypes() {
	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	chat := CreatePublicChat("test-chat", &testTimeSource{})
	message := common.NewMessage()
	message.Text = "sample text"
	chat.LastMessage = message
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	initialCursor := ""
	limit := uint64(3)

	nID1 := types.HexBytes("1")
	nID2 := types.HexBytes("2")
	nID3 := types.HexBytes("3")

	allNotifications := []*ActivityCenterNotification{
		{
			ID:        nID1,
			Type:      ActivityCenterNotificationTypeMention,
			ChatID:    chat.ID,
			Timestamp: 1,
		},
		{
			ID:        nID2,
			Type:      ActivityCenterNotificationTypeNewOneToOne,
			ChatID:    chat.ID,
			Timestamp: 1,
		},
		{
			ID:        nID3,
			Type:      ActivityCenterNotificationTypeMention,
			ChatID:    chat.ID,
			Timestamp: 1,
		},
	}

	for _, notification := range allNotifications {
		_, err = p.SaveActivityCenterNotification(notification, true)
		s.Require().NoError(err)
	}

	// Don't filter by type if the array of types is empty.
	_, notifications, err := p.ActivityCenterNotifications(
		initialCursor,
		limit,
		[]ActivityCenterType{},
		ActivityCenterQueryParamsReadUnread,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 3)
	s.Require().Equal(nID3, notifications[0].ID)
	s.Require().Equal(nID2, notifications[1].ID)
	s.Require().Equal(nID1, notifications[2].ID)

	_, notifications, err = p.ActivityCenterNotifications(
		initialCursor,
		limit,
		[]ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
		ActivityCenterQueryParamsReadUnread,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	s.Require().Equal(nID2, notifications[0].ID)

	_, notifications, err = p.ActivityCenterNotifications(
		initialCursor,
		limit,
		[]ActivityCenterType{ActivityCenterNotificationTypeMention},
		ActivityCenterQueryParamsReadUnread,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 2)
	s.Require().Equal(nID3, notifications[0].ID)
	s.Require().Equal(nID1, notifications[1].ID)

	_, notifications, err = p.ActivityCenterNotifications(
		initialCursor,
		limit,
		[]ActivityCenterType{ActivityCenterNotificationTypeMention, ActivityCenterNotificationTypeNewOneToOne},
		ActivityCenterQueryParamsReadUnread,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 3)
	s.Require().Equal(nID3, notifications[0].ID)
	s.Require().Equal(nID2, notifications[1].ID)
	s.Require().Equal(nID1, notifications[2].ID)

	// Mark all notifications as read.
	for _, notification := range allNotifications {
		err = p.MarkActivityCenterNotificationsRead([]types.HexBytes{notification.ID}, currentMilliseconds())
		s.Require().NoError(err)
	}

	_, notifications, err = p.ActivityCenterNotifications(
		initialCursor,
		limit,
		[]ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
		ActivityCenterQueryParamsReadRead,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 1)
	s.Require().Equal(nID2, notifications[0].ID)

	_, notifications, err = p.ActivityCenterNotifications(
		initialCursor,
		limit,
		[]ActivityCenterType{ActivityCenterNotificationTypeMention},
		ActivityCenterQueryParamsReadRead,
		false,
	)
	s.Require().NoError(err)
	s.Require().Len(notifications, 2)
	s.Require().Equal(nID3, notifications[0].ID)
	s.Require().Equal(nID1, notifications[1].ID)
}

func (s *ActivityCenterPersistenceTestSuite) Test_ActivityCenterReadUnread() {
	nID1 := types.HexBytes("1")
	nID2 := types.HexBytes("2")

	db, err := openTestDB()
	s.Require().NoError(err)
	p := newSQLitePersistence(db)

	chat := CreatePublicChat("test-chat", &testTimeSource{})
	message := common.NewMessage()
	message.Text = "sample text"
	chat.LastMessage = message
	err = p.SaveChat(*chat)
	s.Require().NoError(err)

	notification := &ActivityCenterNotification{
		ID:        nID1,
		Type:      ActivityCenterNotificationTypeNewOneToOne,
		ChatID:    chat.ID,
		Timestamp: 1,
	}

	_, err = p.SaveActivityCenterNotification(notification, true)
	s.Require().NoError(err)

	notification = &ActivityCenterNotification{
		ID:        nID2,
		Type:      ActivityCenterNotificationTypeNewOneToOne,
		ChatID:    chat.ID,
		Timestamp: 1,
	}

	_, err = p.SaveActivityCenterNotification(notification, true)
	s.Require().NoError(err)

	// Mark the notification as read
	err = p.MarkActivityCenterNotificationsRead([]types.HexBytes{nID2}, currentMilliseconds())
	s.Require().NoError(err)

	cursor, notifications, err := p.ActivityCenterNotifications(
		"",
		2,
		[]ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
		ActivityCenterQueryParamsReadUnread,
		false,
	)
	s.Require().NoError(err)
	s.Require().Empty(cursor)
	s.Require().Len(notifications, 1)
	s.Require().Equal(nID1, notifications[0].ID)

	cursor, notifications, err = p.ActivityCenterNotifications(
		"",
		2,
		[]ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
		ActivityCenterQueryParamsReadRead,
		false,
	)
	s.Require().NoError(err)
	s.Require().Empty(cursor)
	s.Require().Len(notifications, 1)
	s.Require().Equal(nID2, notifications[0].ID)
}