package protocol

import (
	"context"
	"errors"

	_ "github.com/mutecomm/go-sqlcipher/v4" // require go-sqlcipher that overrides default implementation

	"github.com/status-im/status-go/protocol/common"
	"github.com/status-im/status-go/protocol/protobuf"
	"github.com/status-im/status-go/protocol/requests"
	"github.com/status-im/status-go/protocol/tt"
)

func (s *MessengerSuite) checkMessageSeen(messageID string, expectedSeen bool) {
	message, err := s.m.MessageByID(messageID)

	s.Require().NoError(err)
	s.Require().Equal(expectedSeen, message.Seen)
}

func (s *MessengerSuite) retrieveAllWithRetry(errorMessage string) (*MessengerResponse, error) {
	var response *MessengerResponse
	var err error

	retryFunc := func() error {
		response, err = s.m.RetrieveAll()
		if err != nil {
			return err
		}
		if len(response.messages) == 0 {
			return errors.New(errorMessage)
		}
		return nil
	}

	err = tt.RetryWithBackOff(retryFunc)
	return response, err
}

func (s *MessengerSuite) TestMarkMessageAsUnreadWhenMessageListContainsSingleMessage() {
	chat := CreatePublicChat("test-chat-1", s.m.transport)

	chat.UnviewedMessagesCount = 2
	chat.UnviewedMentionsCount = 2
	chat.Highlight = true

	err := s.m.SaveChat(chat)
	s.Require().NoError(err)

	inputMessage1 := buildTestMessage(*chat)
	inputMessage1.ID = "1"
	inputMessage1.Seen = true
	inputMessage1.Mentioned = true

	err = s.m.SaveMessages([]*common.Message{inputMessage1})
	s.Require().NoError(err)

	_, err = s.m.MarkAllRead(context.Background(), chat.ID)
	s.Require().NoError(err)

	chats := s.m.allChats

	actualChat, ok := chats.Load(chat.ID)

	s.Require().True(ok)
	s.Require().Equal(uint(0), actualChat.UnviewedMessagesCount)
	s.Require().Equal(uint(0), actualChat.UnviewedMentionsCount)
	s.Require().False(actualChat.Highlight)
	s.checkMessageSeen(inputMessage1.ID, true)

	response, err := s.m.MarkMessageAsUnread(chat.ID, inputMessage1.ID)
	s.Require().NoError(err)
	s.Require().Len(response.GetSeenAndUnseenMessages(), 1)
	s.Require().Equal(chat.ID, response.GetSeenAndUnseenMessages()[0].ChatID)
	s.Require().Equal(uint64(1), response.GetSeenAndUnseenMessages()[0].Count)
	s.Require().Equal(uint64(1), response.GetSeenAndUnseenMessages()[0].CountWithMentions)
	s.Require().Equal(false, response.GetSeenAndUnseenMessages()[0].Seen)
	s.Require().Len(response.ActivityCenterNotifications(), 0)

	chats = s.m.allChats

	actualChat, ok = chats.Load(chat.ID)
	s.Require().True(ok)
	s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount)
	s.Require().Equal(uint(1), actualChat.UnviewedMentionsCount)
	s.Require().False(actualChat.Highlight)
	s.checkMessageSeen(inputMessage1.ID, false)
}

func (s *MessengerSuite) TestMarkMessageAsUnreadWhenMessageListContainsSeveralMessages() {
	chat := CreatePublicChat("test-chat-2", s.m.transport)

	chat.UnviewedMessagesCount = 0
	chat.UnviewedMentionsCount = 0
	chat.Highlight = true

	err := s.m.SaveChat(chat)
	s.Require().NoError(err)

	inputMessage1 := buildTestMessage(*chat)
	inputMessage1.ID = "1"
	inputMessage1.Seen = true
	inputMessage1.Mentioned = true
	inputMessage1.Timestamp = 1

	inputMessage2 := buildTestMessage(*chat)
	inputMessage2.ID = "2"
	inputMessage2.Seen = true
	inputMessage2.Mentioned = false
	inputMessage2.Timestamp = 2

	inputMessage3 := buildTestMessage(*chat)
	inputMessage3.ID = "3"
	inputMessage3.Seen = true
	inputMessage3.Mentioned = false
	inputMessage3.Timestamp = 3

	err = s.m.SaveMessages([]*common.Message{
		inputMessage1,
		inputMessage2,
		inputMessage3,
	})
	s.Require().NoError(err)

	_, err = s.m.MarkAllRead(context.Background(), chat.ID)
	s.Require().NoError(err)

	chats := s.m.allChats
	actualChat, ok := chats.Load(chat.ID)

	s.Require().True(ok)
	s.Require().Equal(uint(0), actualChat.UnviewedMessagesCount)
	s.Require().Equal(uint(0), actualChat.UnviewedMentionsCount)
	s.Require().False(actualChat.Highlight)
	s.checkMessageSeen(inputMessage1.ID, true)
	s.checkMessageSeen(inputMessage2.ID, true)
	s.checkMessageSeen(inputMessage3.ID, true)

	response, err := s.m.MarkMessageAsUnread(chat.ID, inputMessage2.ID)
	s.Require().NoError(err)

	// count is 2 because the messages are layout the following way :
	//
	// inputMessage1   read    <-- mentioned
	// ----------------------------- <-- marker
	// inputMessage2   unread
	// inputMessage3   unread
	//
	// And the inputMessage3 has greater timestamp than inputMessage2
	// Similarly, the mentioned message is inputMessage1, therefore the
	// countWithMentions is 0
	s.Require().Len(response.GetSeenAndUnseenMessages(), 1)
	s.Require().Equal(chat.ID, response.GetSeenAndUnseenMessages()[0].ChatID)
	s.Require().Equal(uint64(2), response.GetSeenAndUnseenMessages()[0].Count)
	s.Require().Equal(uint64(0), response.GetSeenAndUnseenMessages()[0].CountWithMentions)
	s.Require().Equal(false, response.GetSeenAndUnseenMessages()[0].Seen)

	chats = s.m.allChats
	actualChat, ok = chats.Load(chat.ID)

	s.Require().True(ok)
	s.Require().Equal(uint(2), actualChat.UnviewedMessagesCount)
	s.Require().Equal(uint(0), actualChat.UnviewedMentionsCount)
	s.Require().False(actualChat.Highlight)
	s.checkMessageSeen(inputMessage1.ID, true)
	s.checkMessageSeen(inputMessage2.ID, false)
	s.checkMessageSeen(inputMessage3.ID, false)
}

func (s *MessengerSuite) TestMarkMessageAsUnreadWhenMessageIsAlreadyInUnreadState() {
	chat := CreatePublicChat("test-chat-3", s.m.transport)

	chat.UnviewedMessagesCount = 1
	chat.UnviewedMentionsCount = 0
	chat.Highlight = true

	err := s.m.SaveChat(chat)
	s.Require().NoError(err)

	inputMessage1 := buildTestMessage(*chat)
	inputMessage1.ID = "1"
	inputMessage1.Seen = false
	inputMessage1.Mentioned = false

	err = s.m.SaveMessages([]*common.Message{inputMessage1})
	s.Require().NoError(err)

	chats := s.m.allChats
	actualChat, ok := chats.Load(chat.ID)

	s.Require().True(ok)
	s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount)
	s.Require().Equal(uint(0), actualChat.UnviewedMentionsCount)
	s.Require().True(actualChat.Highlight)
	s.checkMessageSeen(inputMessage1.ID, false)

	response, err := s.m.MarkMessageAsUnread(chat.ID, inputMessage1.ID)
	s.Require().NoError(err)
	s.Require().Len(response.GetSeenAndUnseenMessages(), 1)
	s.Require().Equal(chat.ID, response.GetSeenAndUnseenMessages()[0].ChatID)
	s.Require().Equal(uint64(1), response.GetSeenAndUnseenMessages()[0].Count)
	s.Require().Equal(uint64(0), response.GetSeenAndUnseenMessages()[0].CountWithMentions)
	s.Require().Equal(false, response.GetSeenAndUnseenMessages()[0].Seen)

	chats = s.m.allChats
	actualChat, ok = chats.Load(chat.ID)

	s.Require().True(ok)
	s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount)
	s.Require().Equal(uint(0), actualChat.UnviewedMentionsCount)
	s.Require().False(actualChat.Highlight)
	s.checkMessageSeen(inputMessage1.ID, false)
}

func (s *MessengerSuite) TestMarkMessageAsUnreadInOneChatDoesNotImpactOtherChats() {
	chat1 := CreatePublicChat("test-chat-1", s.m.transport)
	err := s.m.SaveChat(chat1)
	s.Require().NoError(err)

	chat2 := CreatePublicChat("test-chat-2", s.m.transport)
	err = s.m.SaveChat(chat2)
	s.Require().NoError(err)

	chat1.UnviewedMessagesCount = 2
	chat1.UnviewedMentionsCount = 0
	chat1.Highlight = true

	inputMessage1 := buildTestMessage(*chat1)
	inputMessage1.ID = "1"
	inputMessage1.Seen = true
	inputMessage1.Mentioned = false

	inputMessage2 := buildTestMessage(*chat1)
	inputMessage2.ID = "2"
	inputMessage2.Seen = true
	inputMessage2.Mentioned = false

	err = s.m.SaveMessages([]*common.Message{inputMessage1, inputMessage2})
	s.Require().NoError(err)

	chat2.UnviewedMessagesCount = 1
	chat2.UnviewedMentionsCount = 0
	chat2.Highlight = true

	inputMessage3 := buildTestMessage(*chat2)
	inputMessage3.ID = "3"
	inputMessage3.Seen = false
	inputMessage3.Mentioned = false

	err = s.m.SaveMessages([]*common.Message{inputMessage3})
	s.Require().NoError(err)

	_, err = s.m.MarkAllRead(context.Background(), chat1.ID)
	s.Require().NoError(err)
	s.checkMessageSeen(inputMessage1.ID, true)
	s.checkMessageSeen(inputMessage2.ID, true)

	response, err := s.m.MarkMessageAsUnread(chat1.ID, inputMessage1.ID)

	s.Require().NoError(err)
	s.Require().Len(response.GetSeenAndUnseenMessages(), 1)
	s.Require().Equal(chat1.ID, response.GetSeenAndUnseenMessages()[0].ChatID)
	s.Require().Equal(uint64(2), response.GetSeenAndUnseenMessages()[0].Count)
	s.Require().Equal(uint64(0), response.GetSeenAndUnseenMessages()[0].CountWithMentions)
	s.Require().Equal(false, response.GetSeenAndUnseenMessages()[0].Seen)
	s.Require().Len(response.ActivityCenterNotifications(), 0)

	chats := s.m.allChats
	actualChat, ok := chats.Load(chat1.ID)

	s.Require().True(ok)
	s.Require().Equal(uint(2), actualChat.UnviewedMessagesCount)
	s.Require().Equal(uint(0), actualChat.UnviewedMentionsCount)
	s.Require().False(actualChat.Highlight)
	s.checkMessageSeen(inputMessage1.ID, false)
	s.checkMessageSeen(inputMessage2.ID, false)

	actualChat, ok = chats.Load(chat2.ID)
	s.Require().True(ok)
	s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount)
	s.Require().Equal(uint(0), actualChat.UnviewedMentionsCount)
	s.Require().True(actualChat.Highlight)

	s.checkMessageSeen(inputMessage3.ID, false)
}

func (s *MessengerSuite) TestMarkMessageWithNotificationAsUnreadInCommunityChatSetsNotificationAsUnread() {
	other := s.newMessenger()

	description := &requests.CreateCommunity{
		Membership:  protobuf.CommunityPermissions_AUTO_ACCEPT,
		Name:        "status",
		Color:       "#ffffff",
		Description: "This is just a test description for the community",
	}

	response, err := other.CreateCommunity(description, true)
	s.Require().NoError(err)
	s.Require().NotNil(response)
	s.Require().Len(response.Communities(), 1)

	community := response.Communities()[0]
	communityChat := response.Chats()[0]

	_, err = community.AddMember(&s.m.identity.PublicKey, []protobuf.CommunityMember_Roles{}, community.Clock())
	s.Require().NoError(err)

	err = other.communitiesManager.SaveCommunity(community)
	s.Require().NoError(err)

	advertiseCommunityToUserOldWay(&s.Suite, community, other, s.m)

	inputMessage1 := buildTestMessage(*communityChat)
	inputMessage1.ChatId = communityChat.ID
	inputMessage1.ContentType = protobuf.ChatMessage_TEXT_PLAIN
	inputMessage1.Text = "Hello @" + common.EveryoneMentionTag + " !"

	sendResponse, err := other.SendChatMessage(context.Background(), inputMessage1)
	s.NoError(err)
	s.Require().Len(sendResponse.Messages(), 1)

	response, err = s.retrieveAllWithRetry("message from other chatter not received")

	s.Require().NoError(err)
	s.Require().Len(response.Chats(), 1)
	s.Require().Len(response.Messages(), 1)
	s.Require().Len(response.ActivityCenterNotifications(), 1)
	s.Require().False(response.ActivityCenterNotifications()[0].Read)

	response, err = s.m.MarkAllRead(context.Background(), communityChat.ID)

	s.Require().NoError(err)
	s.Require().Len(response.ActivityCenterNotifications(), 1)
	s.Require().True(response.ActivityCenterNotifications()[0].Read)

	response, err = s.m.MarkMessageAsUnread(communityChat.ID, inputMessage1.ID)

	s.Require().NoError(err)
	s.Require().Len(response.GetSeenAndUnseenMessages(), 1)
	s.Require().Equal(communityChat.ID, response.GetSeenAndUnseenMessages()[0].ChatID)
	s.Require().Equal(uint64(1), response.GetSeenAndUnseenMessages()[0].Count)
	s.Require().Equal(uint64(1), response.GetSeenAndUnseenMessages()[0].CountWithMentions)
	s.Require().Equal(false, response.GetSeenAndUnseenMessages()[0].Seen)
	s.Require().Len(response.ActivityCenterNotifications(), 1)
	s.Require().False(response.ActivityCenterNotifications()[0].Read)
}