package cache

import (
	"context"
	relationTb "github.com/OpenIMSDK/Open-IM-Server/pkg/common/db/table/relation"
	unrelation2 "github.com/OpenIMSDK/Open-IM-Server/pkg/common/db/table/unrelation"
	"github.com/OpenIMSDK/Open-IM-Server/pkg/utils"
	"github.com/dtm-labs/rockscache"
	"github.com/go-redis/redis/v8"
	"math/big"
	"strings"
	"time"
)

const (
	groupExpireTime      = time.Second * 60 * 60 * 12
	groupInfoKey         = "GROUP_INFO:"
	groupMemberIDsKey    = "GROUP_MEMBER_IDS:"
	groupMembersHashKey  = "GROUP_MEMBERS_HASH:"
	groupMemberInfoKey   = "GROUP_MEMBER_INFO:"
	joinedSuperGroupsKey = "JOIN_SUPER_GROUPS:"
	joinedGroupsKey      = "JOIN_GROUPS_KEY:"
	groupMemberNumKey    = "GROUP_MEMBER_NUM_CACHE:"
)

type GroupCache interface {
	GetGroupsInfo(ctx context.Context, groupIDs []string) (groups []*relationTb.GroupModel, err error)
	GetGroupInfo(ctx context.Context, groupID string) (group *relationTb.GroupModel, err error)
	BatchDelJoinedSuperGroupIDs(ctx context.Context, userIDs []string) (err error)
	DelJoinedSuperGroupIDs(ctx context.Context, userID string) (err error)
	GetJoinedSuperGroupIDs(ctx context.Context, userID string) (joinedSuperGroupIDs []string, err error)
	GetGroupMembersHash(ctx context.Context, groupID string) (hashCodeUint64 uint64, err error)
	GetGroupMemberHash1(ctx context.Context, groupIDs []string) (map[string]*relationTb.GroupSimpleUserID, error)
	DelGroupMembersHash(ctx context.Context, groupID string) (err error)
	GetGroupMemberIDs(ctx context.Context, groupID string) (groupMemberIDs []string, err error)
	DelGroupMemberIDs(ctx context.Context, groupID string) (err error)
	DelJoinedGroupID(ctx context.Context, userID string) (err error)
	GetGroupMemberInfo(ctx context.Context, groupID, userID string) (groupMember *relationTb.GroupMemberModel, err error)
	DelGroupMemberInfo(ctx context.Context, groupID, userID string) (err error)
	DelGroupMemberNum(ctx context.Context, groupID string) (err error)
	DelGroupInfo(ctx context.Context, groupID string) (err error)
	DelGroupsInfo(ctx context.Context, groupIDs []string) error
}

type GroupCacheRedis struct {
	group        relationTb.GroupModelInterface
	groupMember  relationTb.GroupMemberModelInterface
	groupRequest relationTb.GroupRequestModelInterface
	mongoDB      unrelation2.SuperGroupModelInterface
	expireTime   time.Duration
	rcClient     *rockscache.Client
}

func NewGroupCacheRedis(rdb redis.UniversalClient, groupDB relationTb.GroupModelInterface, groupMemberDB relationTb.GroupMemberModelInterface, groupRequestDB relationTb.GroupRequestModelInterface, mongoClient unrelation2.SuperGroupModelInterface, opts rockscache.Options) GroupCache {
	return &GroupCacheRedis{rcClient: rockscache.NewClient(rdb, opts), expireTime: groupExpireTime,
		group: groupDB, groupMember: groupMemberDB, groupRequest: groupRequestDB,
		mongoDB: mongoClient,
	}
}

func (g *GroupCacheRedis) getGroupInfoKey(groupID string) string {
	return groupInfoKey + groupID
}

func (g *GroupCacheRedis) getJoinedSuperGroupsIDKey(userID string) string {
	return joinedSuperGroupsKey + userID
}

func (g *GroupCacheRedis) getJoinedGroupsKey(userID string) string {
	return joinedGroupsKey + userID
}

func (g *GroupCacheRedis) getGroupMembersHashKey(groupID string) string {
	return groupMembersHashKey + groupID
}

func (g *GroupCacheRedis) getGroupMemberIDsKey(groupID string) string {
	return groupMemberIDsKey + groupID
}

func (g *GroupCacheRedis) getGroupMemberInfoKey(groupID, userID string) string {
	return groupMemberInfoKey + groupID + "-" + userID
}

func (g *GroupCacheRedis) getGroupMemberNumKey(groupID string) string {
	return groupMemberNumKey + groupID
}

// / groupInfo
func (g *GroupCacheRedis) GetGroupsInfo(ctx context.Context, groupIDs []string) (groups []*relationTb.GroupModel, err error) {
	return GetCacheFor(ctx, groupIDs, func(ctx context.Context, groupID string) (*relationTb.GroupModel, error) {
		return g.GetGroupInfo(ctx, groupID)
	})
}

func (g *GroupCacheRedis) GetGroupInfo(ctx context.Context, groupID string) (group *relationTb.GroupModel, err error) {
	return GetCache(ctx, g.rcClient, g.getGroupInfoKey(groupID), g.expireTime, func(ctx context.Context) (*relationTb.GroupModel, error) {
		return g.group.Take(ctx, groupID)
	})
}

// userJoinSuperGroup
func (g *GroupCacheRedis) BatchDelJoinedSuperGroupIDs(ctx context.Context, userIDs []string) (err error) {
	for _, userID := range userIDs {
		if err := g.DelJoinedSuperGroupIDs(ctx, userID); err != nil {
			return err
		}
	}
	return nil
}

func (g *GroupCacheRedis) DelJoinedSuperGroupIDs(ctx context.Context, userID string) (err error) {
	return g.rcClient.TagAsDeleted(g.getJoinedSuperGroupsIDKey(userID))
}

func (g *GroupCacheRedis) GetJoinedSuperGroupIDs(ctx context.Context, userID string) (joinedSuperGroupIDs []string, err error) {
	return GetCache(ctx, g.rcClient, g.getJoinedSuperGroupsIDKey(userID), g.expireTime, func(ctx context.Context) ([]string, error) {
		userGroup, err := g.mongoDB.GetSuperGroupByUserID(ctx, userID)
		if err != nil {
			return nil, err
		}
		return userGroup.GroupIDs, nil
	})
}

// groupMembersHash
func (g *GroupCacheRedis) GetGroupMembersHash(ctx context.Context, groupID string) (hashCodeUint64 uint64, err error) {
	return GetCache(ctx, g.rcClient, g.getGroupMembersHashKey(groupID), g.expireTime, func(ctx context.Context) (uint64, error) {
		userIDs, err := g.GetGroupMemberIDs(ctx, groupID)
		if err != nil {
			return 0, err
		}
		utils.Sort(userIDs, true)
		bi := big.NewInt(0)
		bi.SetString(utils.Md5(strings.Join(userIDs, ";"))[0:8], 16)
		return bi.Uint64(), nil
	})
}

func (g *GroupCacheRedis) GetGroupMemberHash1(ctx context.Context, groupIDs []string) (map[string]*relationTb.GroupSimpleUserID, error) {
	// todo
	mapGroupUserIDs, err := g.groupMember.FindJoinUserID(ctx, groupIDs)
	if err != nil {
		return nil, err
	}
	res := make(map[string]*relationTb.GroupSimpleUserID)
	for _, groupID := range groupIDs {
		userIDs := mapGroupUserIDs[groupID]
		users := &relationTb.GroupSimpleUserID{}
		if len(userIDs) > 0 {
			utils.Sort(userIDs, true)
			bi := big.NewInt(0)
			bi.SetString(utils.Md5(strings.Join(userIDs, ";"))[0:8], 16)
			users.Hash = bi.Uint64()
		}
		res[groupID] = users
	}
	return res, nil
}

func (g *GroupCacheRedis) DelGroupMembersHash(ctx context.Context, groupID string) (err error) {
	return g.rcClient.TagAsDeleted(g.getGroupMembersHashKey(groupID))
}

// groupMemberIDs
func (g *GroupCacheRedis) GetGroupMemberIDs(ctx context.Context, groupID string) (groupMemberIDs []string, err error) {
	return GetCache(ctx, g.rcClient, g.getGroupMemberIDsKey(groupID), g.expireTime, func(ctx context.Context) ([]string, error) {
		return g.groupMember.FindMemberUserID(ctx, groupID)
	})
}

func (g *GroupCacheRedis) DelGroupMemberIDs(ctx context.Context, groupID string) (err error) {
	return g.rcClient.TagAsDeleted(g.getGroupMemberIDsKey(groupID))
}

//// JoinedGroups
//func (g *GroupCacheRedis) GetJoinedGroupIDs(ctx context.Context, userID string) (joinedGroupIDs []string, err error) {
//	getJoinedGroupIDList := func() (string, error) {
//		joinedGroupList, err := relation.GetJoinedGroupIDListByUserID(userID)
//		if err != nil {
//			return "", err
//		}
//		bytes, err := json.Marshal(joinedGroupList)
//		if err != nil {
//			return "", utils.Wrap(err, "")
//		}
//		return string(bytes), nil
//	}
//	defer func() {
//		mcontext.SetCtxDebug(ctx, utils.GetFuncName(1), err, "userID", userID, "joinedGroupIDs", joinedGroupIDs)
//	}()
//	joinedGroupIDListStr, err := g.rcClient.Fetch(g.getJoinedGroupsKey(userID), time.Second*30*60, getJoinedGroupIDList)
//	if err != nil {
//		return nil, err
//	}
//	err = json.Unmarshal([]byte(joinedGroupIDListStr), &joinedGroupIDs)
//	return joinedGroupIDs, utils.Wrap(err, "")
//}

func (g *GroupCacheRedis) DelJoinedGroupID(ctx context.Context, userID string) (err error) {
	return g.rcClient.TagAsDeleted(g.getJoinedGroupsKey(userID))
}

//func (g *GroupCacheRedis) DelJoinedGroupIDs(ctx context.Context, userIDs []string) (err error) {
//	defer func() {
//		mcontext.SetCtxDebug(ctx, utils.GetFuncName(1), err, "userID", userID)
//	}()
//	for _, userID := range userIDs {
//		if err := g.DelJoinedGroupID(ctx, userID); err != nil {
//			return err
//		}
//	}
//	return nil
//}

func (g *GroupCacheRedis) GetGroupMemberInfo(ctx context.Context, groupID, userID string) (groupMember *relationTb.GroupMemberModel, err error) {
	return GetCache(ctx, g.rcClient, g.getGroupMemberInfoKey(groupID, userID), g.expireTime, func(ctx context.Context) (*relationTb.GroupMemberModel, error) {
		return g.groupMember.Take(ctx, groupID, userID)
	})
}

//func (g *GroupCacheRedis) GetGroupMembersInfo(ctx context.Context, groupID, userIDs []string) (groupMember *relationTb.GroupMemberModel, err error) {
//
//	return nil, err
//}

//func (g *GroupCacheRedis) GetGroupMembersInfo(ctx context.Context, count, offset int32, groupID string) (groupMembers []*relation.GroupMember, err error) {
//	defer func() {
//		mcontext.SetCtxDebug(ctx, utils.GetFuncName(1), err, "count", count, "offset", offset, "groupID", groupID, "groupMember", groupMembers)
//	}()
//	groupMemberIDList, err := g.GetGroupMemberIDs(ctx, groupID)
//	if err != nil {
//		return nil, err
//	}
//	if count < 0 || offset < 0 {
//		return nil, nil
//	}
//	var groupMemberList []*relation.GroupMember
//	var start, stop int32
//	start = offset
//	stop = offset + count
//	l := int32(len(groupMemberIDList))
//	if start > stop {
//		return nil, nil
//	}
//	if start >= l {
//		return nil, nil
//	}
//	if count != 0 {
//		if stop >= l {
//			stop = l
//		}
//		groupMemberIDList = groupMemberIDList[start:stop]
//	} else {
//		if l < 1000 {
//			stop = l
//		} else {
//			stop = 1000
//		}
//		groupMemberIDList = groupMemberIDList[start:stop]
//	}
//	for _, userID := range groupMemberIDList {
//		groupMember, err := g.GetGroupMemberInfo(ctx, groupID, userID)
//		if err != nil {
//			return
//		}
//		groupMembers = append(groupMembers, groupMember)
//	}
//	return groupMemberList, nil
//}

func (g *GroupCacheRedis) DelGroupMemberInfo(ctx context.Context, groupID, userID string) (err error) {
	return g.rcClient.TagAsDeleted(g.getGroupMemberInfoKey(groupID, userID))
}

// groupMemberNum
//func (g *GroupCacheRedis) GetGroupMemberNum(ctx context.Context, groupID string) (num int, err error) {
//	getGroupMemberNum := func() (string, error) {
//		num, err := relation.GetGroupMemberNumByGroupID(groupID)
//		if err != nil {
//			return "", err
//		}
//		return strconv.Itoa(int(num)), nil
//	}
//	defer func() {
//		mcontext.SetCtxDebug(ctx, utils.GetFuncName(1), err, "groupID", groupID, "num", num)
//	}()
//	groupMember, err := g.rcClient.Fetch(g.getGroupMemberNumKey(groupID), time.Second*30*60, getGroupMemberNum)
//	if err != nil {
//		return 0, err
//	}
//	return strconv.Atoi(groupMember)
//}

func (g *GroupCacheRedis) DelGroupMemberNum(ctx context.Context, groupID string) (err error) {
	return g.rcClient.TagAsDeleted(g.getGroupMemberNumKey(groupID))
}

func (g *GroupCacheRedis) DelGroupInfo(ctx context.Context, groupID string) (err error) {
	return g.rcClient.TagAsDeleted(g.getGroupInfoKey(groupID))
}

func (g *GroupCacheRedis) DelGroupsInfo(ctx context.Context, groupIDs []string) error {
	for _, groupID := range groupIDs {
		if err := g.DelGroupInfo(ctx, groupID); err != nil {
			return err
		}
	}
	return nil
}
