package protocol

import (
	"fmt"
	"testing"

	"github.com/status-im/status-go/eth-node/crypto"
	"github.com/status-im/status-go/eth-node/types"
	"github.com/status-im/status-go/protocol/common"
	"github.com/status-im/status-go/protocol/communities"
	"github.com/status-im/status-go/protocol/protobuf"
	"github.com/status-im/status-go/protocol/requests"

	"github.com/stretchr/testify/suite"
)

func TestMessengerCommunityMetricsSuite(t *testing.T) {
	suite.Run(t, new(MessengerCommunityMetricsSuite))
}

type MessengerCommunityMetricsSuite struct {
	MessengerBaseTestSuite
}

func (s *MessengerCommunityMetricsSuite) prepareCommunityAndChatIDs() (*communities.Community, []string) {
	description := &requests.CreateCommunity{
		Membership:  protobuf.CommunityPermissions_AUTO_ACCEPT,
		Name:        "status",
		Color:       "#ffffff",
		Description: "status community description",
	}
	response, err := s.m.CreateCommunity(description, true)
	s.Require().NoError(err)
	s.Require().NotNil(response)

	s.Require().Len(response.Communities(), 1)
	community := response.Communities()[0]

	s.Require().Len(community.ChatIDs(), 1)
	chatIDs := community.ChatIDs()

	// Create another chat
	chat := &protobuf.CommunityChat{
		Permissions: &protobuf.CommunityPermissions{
			Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
		},
		Identity: &protobuf.ChatIdentity{
			DisplayName: "status",
			Emoji:       "👍",
			Description: "status community chat",
		},
	}
	response, err = s.m.CreateCommunityChat(community.ID(), chat)
	s.Require().NoError(err)
	s.Require().NotNil(response)
	s.Require().Len(response.Communities(), 1)
	s.Require().Len(response.Chats(), 1)

	chatIDs = append(chatIDs, response.Chats()[0].ID)

	return community, chatIDs
}

func (s *MessengerCommunityMetricsSuite) prepareCommunityChatMessages(communityID string, chatIDs []string) {
	s.generateMessages(chatIDs[0], communityID, []uint64{
		// out ouf range messages in the beginning
		1690162000,
		// 1st column, 1 message
		1690372200,
		// 2nd column, 1 message
		1690372800,
		// 3rd column, 1 message
		1690373000,
		// out ouf range messages in the end
		1690373100,
	})

	s.generateMessages(chatIDs[1], communityID, []uint64{
		// out ouf range messages in the beginning
		1690151000,
		// 1st column, 2 messages
		1690372000,
		1690372100,
		// 2nd column, 1 message
		1690372700,
		// 3rd column empty
		// out ouf range messages in the end
		1690373100,
	})
}

func (s *MessengerCommunityMetricsSuite) generateMessages(chatID string, communityID string, timestamps []uint64) {
	var messages []*common.Message
	for i, timestamp := range timestamps {
		message := &common.Message{
			ChatMessage: &protobuf.ChatMessage{
				ChatId:      chatID,
				Text:        fmt.Sprintf("Test message %d", i),
				MessageType: protobuf.MessageType_ONE_TO_ONE,
				// NOTE: should we filter content types for messages metrics
				Clock:     timestamp,
				Timestamp: timestamp,
			},
			WhisperTimestamp: timestamp,
			From:             common.PubkeyToHex(&s.m.identity.PublicKey),
			LocalChatID:      chatID,
			CommunityID:      communityID,
			ID:               types.EncodeHex(crypto.Keccak256([]byte(fmt.Sprintf("%s%s%d", chatID, communityID, timestamp)))),
		}

		err := message.PrepareContent(common.PubkeyToHex(&s.m.identity.PublicKey))
		s.Require().NoError(err)

		messages = append(messages, message)
	}
	err := s.m.persistence.SaveMessages(messages)
	s.Require().NoError(err)
}

func (s *MessengerCommunityMetricsSuite) TestCollectCommunityMetricsInvalidRequest() {
	community, _ := s.prepareCommunityAndChatIDs()

	request := &requests.CommunityMetricsRequest{
		CommunityID: community.ID(),
		Type:        requests.CommunityMetricsRequestMessagesTimestamps,
		Intervals: []requests.MetricsIntervalRequest{
			requests.MetricsIntervalRequest{
				StartTimestamp: 1690372400,
				EndTimestamp:   1690371800,
			},
			requests.MetricsIntervalRequest{
				StartTimestamp: 1690371900,
				EndTimestamp:   1690373000,
			},
		},
	}

	// Expect error
	_, err := s.m.CollectCommunityMetrics(request)
	s.Require().Error(err)
	s.Require().Equal(err, requests.ErrInvalidTimestampIntervals)
}

func (s *MessengerCommunityMetricsSuite) TestCollectCommunityMetricsEmptyInterval() {
	community, _ := s.prepareCommunityAndChatIDs()

	request := &requests.CommunityMetricsRequest{
		CommunityID: community.ID(),
		Type:        requests.CommunityMetricsRequestMessagesTimestamps,
	}

	// Expect empty metrics
	resp, err := s.m.CollectCommunityMetrics(request)
	s.Require().NoError(err)
	s.Require().NotNil(resp)

	// Entries count should be empty
	s.Require().Len(resp.Intervals, 0)
}

func (s *MessengerCommunityMetricsSuite) TestCollectCommunityMessagesTimestamps() {
	community, chatIDs := s.prepareCommunityAndChatIDs()

	s.prepareCommunityChatMessages(string(community.ID()), chatIDs)

	// Request metrics
	request := &requests.CommunityMetricsRequest{
		CommunityID: community.ID(),
		Type:        requests.CommunityMetricsRequestMessagesTimestamps,
		Intervals: []requests.MetricsIntervalRequest{
			requests.MetricsIntervalRequest{
				StartTimestamp: 1690372000,
				EndTimestamp:   1690372300,
			},
			requests.MetricsIntervalRequest{
				StartTimestamp: 1690372400,
				EndTimestamp:   1690372800,
			},
			requests.MetricsIntervalRequest{
				StartTimestamp: 1690372900,
				EndTimestamp:   1690373000,
			},
		},
	}

	resp, err := s.m.CollectCommunityMetrics(request)
	s.Require().NoError(err)
	s.Require().NotNil(resp)

	s.Require().Len(resp.Intervals, 3)

	s.Require().Equal(resp.Intervals[0].Count, 3)
	s.Require().Equal(resp.Intervals[1].Count, 2)
	s.Require().Equal(resp.Intervals[2].Count, 1)

	s.Require().Equal(resp.Intervals[0].Timestamps, []uint64{1690372000, 1690372100, 1690372200})
	s.Require().Equal(resp.Intervals[1].Timestamps, []uint64{1690372700, 1690372800})
	s.Require().Equal(resp.Intervals[2].Timestamps, []uint64{1690373000})
}

func (s *MessengerCommunityMetricsSuite) TestCollectCommunityMessagesCount() {
	community, chatIDs := s.prepareCommunityAndChatIDs()

	s.prepareCommunityChatMessages(string(community.ID()), chatIDs)

	// Request metrics
	request := &requests.CommunityMetricsRequest{
		CommunityID: community.ID(),
		Type:        requests.CommunityMetricsRequestMessagesCount,
		Intervals: []requests.MetricsIntervalRequest{
			requests.MetricsIntervalRequest{
				StartTimestamp: 1690372000,
				EndTimestamp:   1690372300,
			},
			requests.MetricsIntervalRequest{
				StartTimestamp: 1690372400,
				EndTimestamp:   1690372800,
			},
			requests.MetricsIntervalRequest{
				StartTimestamp: 1690372900,
				EndTimestamp:   1690373000,
			},
		},
	}

	resp, err := s.m.CollectCommunityMetrics(request)
	s.Require().NoError(err)
	s.Require().NotNil(resp)

	s.Require().Len(resp.Intervals, 3)

	s.Require().Equal(resp.Intervals[0].Count, 3)
	s.Require().Equal(resp.Intervals[1].Count, 2)
	s.Require().Equal(resp.Intervals[2].Count, 1)
}