package jssdk

import (
	"context"
	"github.com/gin-gonic/gin"
	"github.com/openimsdk/protocol/conversation"
	"github.com/openimsdk/protocol/group"
	"github.com/openimsdk/protocol/jssdk"
	"github.com/openimsdk/protocol/msg"
	"github.com/openimsdk/protocol/relation"
	"github.com/openimsdk/protocol/sdkws"
	"github.com/openimsdk/protocol/user"
	"github.com/openimsdk/tools/mcontext"
	"github.com/openimsdk/tools/utils/datautil"
	"sort"
)

const (
	maxGetActiveConversation     = 500
	defaultGetActiveConversation = 100
)

func NewJSSdkApi(user user.UserClient, friend relation.FriendClient, group group.GroupClient, msg msg.MsgClient, conv conversation.ConversationClient) *JSSdk {
	return &JSSdk{
		user:   user,
		friend: friend,
		group:  group,
		msg:    msg,
		conv:   conv,
	}
}

type JSSdk struct {
	user   user.UserClient
	friend relation.FriendClient
	group  group.GroupClient
	msg    msg.MsgClient
	conv   conversation.ConversationClient
}

func (x *JSSdk) GetActiveConversations(c *gin.Context) {
	call(c, x.getActiveConversations)
}

func (x *JSSdk) GetConversations(c *gin.Context) {
	call(c, x.getConversations)
}

func (x *JSSdk) fillConversations(ctx context.Context, conversations []*jssdk.ConversationMsg) error {
	if len(conversations) == 0 {
		return nil
	}
	var (
		userIDs  []string
		groupIDs []string
	)
	for _, c := range conversations {
		if c.Conversation.GroupID == "" {
			userIDs = append(userIDs, c.Conversation.UserID)
		} else {
			groupIDs = append(groupIDs, c.Conversation.GroupID)
		}
	}
	var (
		userMap   map[string]*sdkws.UserInfo
		friendMap map[string]*relation.FriendInfoOnly
		groupMap  map[string]*sdkws.GroupInfo
	)
	if len(userIDs) > 0 {
		users, err := field(ctx, x.user.GetDesignateUsers, &user.GetDesignateUsersReq{UserIDs: userIDs}, (*user.GetDesignateUsersResp).GetUsersInfo)
		if err != nil {
			return err
		}
		friends, err := field(ctx, x.friend.GetFriendInfo, &relation.GetFriendInfoReq{OwnerUserID: conversations[0].Conversation.OwnerUserID, FriendUserIDs: userIDs}, (*relation.GetFriendInfoResp).GetFriendInfos)
		if err != nil {
			return err
		}
		userMap = datautil.SliceToMap(users, (*sdkws.UserInfo).GetUserID)
		friendMap = datautil.SliceToMap(friends, (*relation.FriendInfoOnly).GetFriendUserID)
	}
	if len(groupIDs) > 0 {
		resp, err := x.group.GetGroupsInfo(ctx, &group.GetGroupsInfoReq{GroupIDs: groupIDs})
		if err != nil {
			return err
		}
		groupMap = datautil.SliceToMap(resp.GroupInfos, (*sdkws.GroupInfo).GetGroupID)
	}
	for _, c := range conversations {
		if c.Conversation.GroupID == "" {
			c.User = userMap[c.Conversation.UserID]
			c.Friend = friendMap[c.Conversation.UserID]
		} else {
			c.Group = groupMap[c.Conversation.GroupID]
		}
	}
	return nil
}

func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActiveConversationsReq) (*jssdk.GetActiveConversationsResp, error) {
	if req.Count <= 0 || req.Count > maxGetActiveConversation {
		req.Count = defaultGetActiveConversation
	}
	req.OwnerUserID = mcontext.GetOpUserID(ctx)
	conversationIDs, err := field(ctx, x.conv.GetConversationIDs,
		&conversation.GetConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetConversationIDsResp).GetConversationIDs)
	if err != nil {
		return nil, err
	}
	if len(conversationIDs) == 0 {
		return &jssdk.GetActiveConversationsResp{}, nil
	}
	readSeq, err := field(ctx, x.msg.GetHasReadSeqs,
		&msg.GetHasReadSeqsReq{UserID: req.OwnerUserID, ConversationIDs: conversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
	if err != nil {
		return nil, err
	}
	activeConversation, err := field(ctx, x.msg.GetActiveConversation,
		&msg.GetActiveConversationReq{ConversationIDs: conversationIDs}, (*msg.GetActiveConversationResp).GetConversations)
	if err != nil {
		return nil, err
	}
	if len(activeConversation) == 0 {
		return &jssdk.GetActiveConversationsResp{}, nil
	}
	sortConversations := sortActiveConversations{
		Conversation: activeConversation,
	}
	if len(activeConversation) > 1 {
		pinnedConversationIDs, err := field(ctx, x.conv.GetPinnedConversationIDs,
			&conversation.GetPinnedConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetPinnedConversationIDsResp).GetConversationIDs)
		if err != nil {
			return nil, err
		}
		sortConversations.PinnedConversationIDs = datautil.SliceSet(pinnedConversationIDs)
	}
	sort.Sort(&sortConversations)
	sortList := sortConversations.Top(int(req.Count))
	conversations, err := field(ctx, x.conv.GetConversations,
		&conversation.GetConversationsReq{
			OwnerUserID: req.OwnerUserID,
			ConversationIDs: datautil.Slice(sortList, func(c *msg.ActiveConversation) string {
				return c.ConversationID
			})}, (*conversation.GetConversationsResp).GetConversations)
	if err != nil {
		return nil, err
	}
	msgs, err := field(ctx, x.msg.GetSeqMessage,
		&msg.GetSeqMessageReq{
			UserID: req.OwnerUserID,
			Conversations: datautil.Slice(sortList, func(c *msg.ActiveConversation) *msg.ConversationSeqs {
				return &msg.ConversationSeqs{
					ConversationID: c.ConversationID,
					Seqs:           []int64{c.MaxSeq},
				}
			}),
		}, (*msg.GetSeqMessageResp).GetMsgs)
	if err != nil {
		return nil, err
	}
	conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string {
		return c.ConversationID
	})
	resp := make([]*jssdk.ConversationMsg, 0, len(sortList))
	for _, c := range sortList {
		conv, ok := conversationMap[c.ConversationID]
		if !ok {
			continue
		}
		var lastMsg *sdkws.MsgData
		if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
			lastMsg = msgList.Msgs[0]
		}
		resp = append(resp, &jssdk.ConversationMsg{
			Conversation: conv,
			LastMsg:      lastMsg,
			MaxSeq:       c.MaxSeq,
			ReadSeq:      readSeq[c.ConversationID],
		})
	}
	if err := x.fillConversations(ctx, resp); err != nil {
		return nil, err
	}
	var unreadCount int64
	for _, c := range activeConversation {
		count := c.MaxSeq - readSeq[c.ConversationID]
		if count > 0 {
			unreadCount += count
		}
	}
	return &jssdk.GetActiveConversationsResp{
		Conversations: resp,
		UnreadCount:   unreadCount,
	}, nil
}

func (x *JSSdk) getConversations(ctx context.Context, req *jssdk.GetConversationsReq) (*jssdk.GetConversationsResp, error) {
	req.OwnerUserID = mcontext.GetOpUserID(ctx)
	conversations, err := field(ctx, x.conv.GetConversations, &conversation.GetConversationsReq{OwnerUserID: req.OwnerUserID, ConversationIDs: req.ConversationIDs}, (*conversation.GetConversationsResp).GetConversations)
	if err != nil {
		return nil, err
	}
	if len(conversations) == 0 {
		return &jssdk.GetConversationsResp{}, nil
	}
	req.ConversationIDs = datautil.Slice(conversations, func(c *conversation.Conversation) string {
		return c.ConversationID
	})
	maxSeqs, err := field(ctx, x.msg.GetMaxSeqs,
		&msg.GetMaxSeqsReq{ConversationIDs: req.ConversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
	if err != nil {
		return nil, err
	}
	readSeqs, err := field(ctx, x.msg.GetHasReadSeqs,
		&msg.GetHasReadSeqsReq{UserID: req.OwnerUserID, ConversationIDs: req.ConversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
	if err != nil {
		return nil, err
	}
	conversationSeqs := make([]*msg.ConversationSeqs, 0, len(conversations))
	for _, c := range conversations {
		if seq := maxSeqs[c.ConversationID]; seq > 0 {
			conversationSeqs = append(conversationSeqs, &msg.ConversationSeqs{
				ConversationID: c.ConversationID,
				Seqs:           []int64{seq},
			})
		}
	}
	var msgs map[string]*sdkws.PullMsgs
	if len(conversationSeqs) > 0 {
		msgs, err = field(ctx, x.msg.GetSeqMessage,
			&msg.GetSeqMessageReq{UserID: req.OwnerUserID, Conversations: conversationSeqs}, (*msg.GetSeqMessageResp).GetMsgs)
		if err != nil {
			return nil, err
		}
	}
	resp := make([]*jssdk.ConversationMsg, 0, len(conversations))
	for _, c := range conversations {
		var lastMsg *sdkws.MsgData
		if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
			lastMsg = msgList.Msgs[0]
		}
		resp = append(resp, &jssdk.ConversationMsg{
			Conversation: c,
			LastMsg:      lastMsg,
			MaxSeq:       maxSeqs[c.ConversationID],
			ReadSeq:      readSeqs[c.ConversationID],
		})
	}
	if err := x.fillConversations(ctx, resp); err != nil {
		return nil, err
	}
	var unreadCount int64
	for conversationID, maxSeq := range maxSeqs {
		count := maxSeq - readSeqs[conversationID]
		if count > 0 {
			unreadCount += count
		}
	}
	return &jssdk.GetConversationsResp{
		Conversations: resp,
		UnreadCount:   unreadCount,
	}, nil
}
