package main

import (
	"errors"
	"fmt"
	"strconv"
	"time"

	"mongo.games.com/goserver/core/basic"
	"mongo.games.com/goserver/core/logger"
	"mongo.games.com/goserver/core/module"
	"mongo.games.com/goserver/core/task"

	"mongo.games.com/game/common"
	"mongo.games.com/game/model"
	"mongo.games.com/game/proto"
	"mongo.games.com/game/protocol/friend"
	"mongo.games.com/game/worldsrv/internal"
)

const (
	ListType_Friend    int32 = iota // 0.好友列表
	ListType_Apply                  // 1.申请列表
	ListType_Recommend              // 2.推荐列表
)

const (
	OpType_Apply  int32 = iota // 0.申请
	OpType_Agree               // 1.同意
	OpType_Refuse              // 2.拒绝
	OpType_Delete              // 3.删除
)

const (
	Invite_Agree  int = iota // 0.同意
	Invite_Refuse            // 1.拒绝
)

const FriendMaxNum = 200
const ShieldMaxNum = 10000
const FriendApplyMaxNum = 1000 //好友申请上限
const FriendWrite = "FriendWrite"

var FriendMgrSington = &FriendMgr{
	FriendList: make(map[string]map[int32]*model.Friend),
	TsInviteCd: make(map[string]map[string]int64),
}

type FriendMgr struct {
	FriendList map[string]map[int32]*model.Friend // 平台id:snid:好友信息
	TsInviteCd map[string]map[string]int64        // 平台id:snid:邀请cd时间
}

func (this *FriendMgr) ModuleName() string {
	return "FriendMgr"
}

func (this *FriendMgr) Init() {
}

// Add 缓存玩家信息
func (this *FriendMgr) Add(platform string, snid int32) {
	logger.Logger.Trace("(this *FriendMgr) Add ", snid)
	if this.FriendList[platform] == nil {
		this.FriendList[platform] = make(map[int32]*model.Friend)
	}
	if _, exist := this.FriendList[platform][snid]; !exist {
		p := PlayerMgrSington.GetPlayerBySnId(snid)
		if p != nil {
			this.FriendList[platform][snid] = model.NewFriend(p.Platform, p.SnId)
			this.UpdateInfo(p.Platform, p.SnId)
		}
	}
}

// Del 删除玩家缓存
func (this *FriendMgr) Del(platform string, snid int32) {
	if this.FriendList[platform] == nil {
		return
	}
	if _, exist := this.FriendList[platform][snid]; exist {
		delete(this.FriendList[platform], snid)
	}
}

// CanInvite 是否可以邀请，邀请冷却
func (this *FriendMgr) CanInvite(platform string, snid, fsnid int32) bool {
	if this.TsInviteCd[platform] == nil {
		this.TsInviteCd[platform] = make(map[string]int64)
	}
	strSnid := strconv.FormatInt(int64(snid), 10) + "_" + strconv.FormatInt(int64(fsnid), 10)
	if _, exist := this.TsInviteCd[platform][strSnid]; !exist {
		this.TsInviteCd[platform][strSnid] = time.Now().Unix()
		return true
	}
	if this.TsInviteCd[platform][strSnid] == 0 {
		this.TsInviteCd[platform][strSnid] = time.Now().Unix()
		return true
	}
	if time.Now().Unix()-this.TsInviteCd[platform][strSnid] > 30 {
		this.TsInviteCd[platform][strSnid] = time.Now().Unix()
		return true
	}
	return false
}

// AgreeApply 同意好友申请
func (this *FriendMgr) AgreeApply(platform string, snid int32, bf *model.BindFriend) friend.OpResultCode {
	logger.Logger.Trace("(this *FriendMgr) AgreeApply ", snid, " BindFriend: ", bf)
	if bf == nil || bf.Platform != platform {
		return friend.OpResultCode_OPRC_Friend_NoPlayer
	}
	if this.FriendList[platform] == nil {
		this.FriendList[platform] = make(map[int32]*model.Friend)
	}

	// 是否为好友
	if this.IsFriend(platform, snid, bf.SnId) {
		return friend.OpResultCode_OPRC_Friend_AlreadyAdd
	}

	// 检查好友数量
	dflDest := this.GetFriendList(platform, snid)
	if dflDest != nil {
		if len(dflDest) >= FriendMaxNum {
			return friend.OpResultCode_OPRC_Friend_DestFriendMax
		}
	}
	dfl := this.GetFriendList(platform, bf.SnId)
	if dfl != nil {
		if len(dfl) >= FriendMaxNum {
			return friend.OpResultCode_OPRC_Friend_FriendMax
		}
	}

	bf.CreateTime = time.Now().Unix()
	this.FriendList[platform][snid].BindFriend = append(this.FriendList[platform][snid].BindFriend, bf)
	this.FriendList[platform][snid].Dirty = true
	return friend.OpResultCode_OPRC_Sucess
}

// RemoveFriend 删除好友
func (this *FriendMgr) RemoveFriend(platform string, snid, destSnid int32) bool {
	logger.Logger.Trace("(this *FriendMgr) RemoveFriend ", snid, " destSnid: ", destSnid)
	if this.FriendList[platform] == nil {
		return false
	}
	if fl, exist := this.FriendList[platform][snid]; exist {
		for i, f := range fl.BindFriend {
			if f.SnId == destSnid {
				fl.BindFriend = append(fl.BindFriend[:i], fl.BindFriend[i+1:]...)
				fl.Dirty = true
				return true
			}
		}
	}

	return false
}

func (this *FriendMgr) UpdateInfo(platform string, snid int32) {
	logger.Logger.Trace("(this *FriendMgr) UpdateInfo ", snid)
	if this.FriendList[platform] == nil {
		return
	}

	p := PlayerMgrSington.GetPlayerBySnId(snid)
	if p == nil {
		return
	}

	info, ok := this.FriendList[platform][snid]
	if !ok {
		return
	}
	info.Name = p.Name
	info.Head = p.Head
	info.HeadUrl = p.HeadUrl
	info.Sex = p.Sex
	info.Coin = p.Coin
	info.Diamond = p.Diamond
	item := BagMgrSingleton.GetItem(p.SnId, VCard)
	if item != nil {
		info.VCard = item.ItemNum
	}
	info.Roles = p.Roles.ModUnlock
	info.Pets = p.Pets.ModUnlock
	info.Age = p.Age
	info.Signature = p.Signature
	info.GameID = p.GameID
	info.UpdateTime = time.Now().Unix()
	info.Dirty = true

	for _, v := range info.BindFriend {
		if v == nil {
			continue
		}
		in := this.FriendList[platform][v.SnId]
		if in == nil {
			continue
		}
		for _, vv := range in.BindFriend {
			if vv != nil && vv.SnId == snid {
				vv.Name = p.Name
				vv.Head = p.Head
				vv.HeadUrl = p.HeadUrl
				vv.Sex = p.Sex
				vv.LogoutTime = p.LastLogoutTime.Unix()
				vv.RoleId = p.Roles.ModId
				in.Dirty = true
				break
			}
		}
	}
}

// GetPlayer 获取玩家信息
func (this *FriendMgr) GetPlayer(platform string, snid int32) *model.Friend {
	if this.FriendList[platform] == nil {
		return nil
	}
	if fi, exist := this.FriendList[platform][snid]; exist {
		this.UpdateInfo(platform, snid)
		return fi
	}
	return nil
}

// GetFriendList 获取好友列表
func (this *FriendMgr) GetFriendList(platform string, snid int32) []*model.BindFriend {
	var allFriend []*model.BindFriend
	if this.FriendList[platform] == nil {
		return nil
	}
	if f, exist := this.FriendList[platform][snid]; exist {
		if f != nil && f.BindFriend != nil {
			for _, bindFriend := range f.BindFriend {
				allFriend = append(allFriend, bindFriend)
			}
		}
	}
	return allFriend
}

// IsFriend 是否好友关系
func (this *FriendMgr) IsFriend(platform string, snid, destSnid int32) bool {
	logger.Logger.Trace("(this *FriendMgr) IsFriend ", snid, " -> ", destSnid)
	dfl := this.GetFriendList(platform, snid)
	if dfl != nil && len(dfl) != 0 {
		for _, df := range dfl {
			if df.SnId == destSnid {
				return true
			}
		}
	}
	return false
}

// ApplyList 查询好友申请列表
func (this *FriendMgr) ApplyList(platform string, snid int32) {
	task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
		ret, err := model.QueryFriendApplyBySnid(platform, snid)
		if err != nil {
			return nil
		}
		return ret
	}), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) {
		if ret, ok := data.(*model.FriendApply); ok && ret != nil && ret.ApplySnids != nil {
			if ret.ApplySnids != nil {
				pack := &friend.SCFriendApplyData{}
				for _, as := range ret.ApplySnids {
					fa := &friend.FriendApply{
						Snid:     proto.Int32(as.SnId),
						Name:     proto.String(as.Name),
						CreateTs: proto.Int64(as.CreateTs),
					}
					pack.FriendApplys = append(pack.FriendApplys, fa)
				}
				if len(pack.FriendApplys) > 0 {
					proto.SetDefaults(pack)
					p := PlayerMgrSington.GetPlayerBySnId(snid)
					if p != nil {
						p.SendToClient(int(friend.FriendPacketID_PACKET_SCFriendApplyData), pack)
						logger.Logger.Trace("SCFriendApplyData: 好友申请列表 pack: ", pack)
					}
				}
			}
		}
	})).StartByFixExecutor("QueryFriendApplyBySnid")
}

// AddShield 屏蔽好友消息
func (this *FriendMgr) AddShield(platform string, snid, ssnid int32) {
	if this.FriendList[platform] == nil {
		return
	}
	if fl, exist := this.FriendList[platform][snid]; exist {
		if fl.Shield == nil {
			fl.Shield = []int32{}
		}
		if len(fl.Shield) > ShieldMaxNum {
			return
		}
		fl.Shield = append(fl.Shield, ssnid)
		fl.Dirty = true
	}
}

// DelShield 解除消息屏蔽
func (this *FriendMgr) DelShield(platform string, snid, ssnid int32) {
	if this.FriendList[platform] == nil {
		return
	}
	if fl, exist := this.FriendList[platform][snid]; exist {
		if fl.Shield == nil {
			return
		}
		if len(fl.Shield) == 0 {
			return
		}
		fl.Shield = common.DelSliceInt32(fl.Shield, ssnid)
		fl.Dirty = true
	}
}

// IsShield 是否屏蔽消息
// snid的玩屏蔽ssnid的消息
func (this *FriendMgr) IsShield(platform string, snid, ssnid int32) bool {
	if this.FriendList[platform] == nil {
		return false
	}
	if fl, exist := this.FriendList[platform][snid]; exist {
		if fl.Shield == nil {
			return false
		}
		if len(fl.Shield) == 0 {
			return false
		}
		return common.InSliceInt32(fl.Shield, ssnid)
	}
	return false
}

// UpdateLogoutTime 更新玩家下线时间
func (this *FriendMgr) UpdateLogoutTime(platform string, snid int32) {
	if this.FriendList[platform] == nil {
		return
	}
	if fl, exist := this.FriendList[platform][snid]; exist {
		fl.LogoutTime = time.Now().Unix()
		fl.Dirty = true
		if fl.BindFriend != nil {
			for _, bf := range fl.BindFriend {
				if this.FriendList[bf.Platform] != nil {
					if data, ok := this.FriendList[bf.Platform][bf.SnId]; ok {
						if data.BindFriend != nil {
							for _, bindFriend := range data.BindFriend {
								if bindFriend.SnId == snid {
									bindFriend.LogoutTime = time.Now().Unix()
									break
								}
							}
						}
					}
				}
			}
		}
	}
}

func (this *FriendMgr) FriendOp(opcode int32, p *Player, destP *model.BindFriend) {
	switch opcode {
	case OpType_Apply:
		logger.Logger.Trace("@Apply friend", p.SnId, " -> ", destP.SnId)
		this.FriendApply(p, destP)
	case OpType_Agree:
		logger.Logger.Trace("@AgreeApply friend", p.SnId, " -> ", destP.SnId)
		this.FriendAgree(p, destP)
	case OpType_Refuse:
		logger.Logger.Trace("@Refuse friend", p.SnId, " -> ", destP.SnId)
		this.FriendRefuse(p, destP)
	case OpType_Delete:
		logger.Logger.Trace("@Delete friend", p.SnId, " -> ", destP.SnId)
		this.FriendDelete(p, destP)
	}
}

// FriendApply 好友申请
// 记录在数据库
func (this *FriendMgr) FriendApply(p *Player, destP *model.BindFriend) {
	SendToClick := func(retCode friend.OpResultCode, self ...bool) {
		pack := &friend.SCFriendOp{
			OpCode:    proto.Int32(OpType_Apply),
			SnId:      proto.Int32(destP.SnId),
			OpRetCode: retCode,
		}
		if len(self) == 0 {
			p.SendToClient(int(friend.FriendPacketID_PACKET_SCFriendOp), pack)
		} else {
			destPs := PlayerMgrSington.GetPlayerBySnId(destP.SnId)
			if destPs != nil && destPs.IsOnLine() {
				roleId := common.DefaultRoleId
				if p.Roles != nil {
					roleId = int(p.Roles.ModId)
				}
				pack.Friend = &friend.FriendInfo{
					SnId:     proto.Int32(p.SnId),
					Name:     proto.String(p.Name),
					Sex:      proto.Int32(p.Sex),
					Head:     proto.Int32(p.Head),
					HeadUrl:  proto.String(p.HeadUrl),
					CreateTs: proto.Int64(time.Now().Unix()),
					LogoutTs: proto.Int64(p.LastLogoutTime.Unix()),
					RoleId:   int32(roleId),
				}
				destPs.SendToClient(int(friend.FriendPacketID_PACKET_SCFriendOp), pack)
			}
		}
		logger.Logger.Tracef(">>FriendApply %d -> %d, %v", p.SnId, destP.SnId, pack)
	}
	if FriendMgrSington.IsFriend(p.Platform, p.SnId, destP.SnId) {
		SendToClick(friend.OpResultCode_OPRC_Friend_AlreadyAdd)
		return
	}
	task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
		ret, err := model.QueryFriendApplyBySnid(p.Platform, destP.SnId)
		if err != nil {
			return friend.OpResultCode_OPRC_Error
		}
		if ret != nil {
			//在申请列表
			if ret.ApplySnids != nil {
				if len(ret.ApplySnids) > FriendApplyMaxNum {
					return friend.OpResultCode_OPRC_Friend_DestApplyFriendMax //对方好友申请已达上限
				}
				for _, as := range ret.ApplySnids {
					if as.SnId == p.SnId {
						return friend.OpResultCode_OPRC_Friend_AlreadyApply //已经申请过好友
					}
				}
			}
		} else {
			//不在申请列表 新增
			ret = model.NewFriendApply(destP.SnId)
		}
		roleId := common.DefaultRoleId
		if p.Roles != nil {
			roleId = int(p.Roles.ModId)
		}
		ret.ApplySnids = append(ret.ApplySnids, &model.FriendApplySnid{
			SnId:     p.SnId,
			Name:     p.Name,
			Head:     p.Head,
			HeadUrl:  p.HeadUrl,
			CreateTs: time.Now().Unix(),
			RoleId:   int32(roleId),
		})
		model.UpsertFriendApply(p.Platform, destP.SnId, ret)
		return friend.OpResultCode_OPRC_Sucess
	}), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) {
		SendToClick(friend.OpResultCode_OPRC_Sucess, false) // 对方
		SendToClick(data.(friend.OpResultCode))             // 自己
	})).StartByFixExecutor(FriendWrite)
}

// FriendAgree 同意好友申请
func (this *FriendMgr) FriendAgree(p *Player, destP *model.BindFriend) {
	SendToClick := func(retCode friend.OpResultCode, self ...bool) {
		pack := &friend.SCFriendOp{
			OpCode:    proto.Int32(OpType_Agree),
			SnId:      proto.Int32(destP.SnId),
			OpRetCode: retCode,
		}
		if len(self) == 0 {
			roleId := common.DefaultRoleId
			if destP.RoleId != 0 {
				roleId = int(destP.RoleId)
			}
			pack.Friend = &friend.FriendInfo{
				SnId:     proto.Int32(destP.SnId),
				Name:     proto.String(destP.Name),
				Sex:      proto.Int32(destP.Sex),
				Head:     proto.Int32(destP.Head),
				HeadUrl:  proto.String(destP.HeadUrl),
				CreateTs: proto.Int64(time.Now().Unix()),
				LogoutTs: proto.Int64(destP.LogoutTime),
				RoleId:   int32(roleId),
			}
			p.SendToClient(int(friend.FriendPacketID_PACKET_SCFriendOp), pack)
		} else {
			destPs := PlayerMgrSington.GetPlayerBySnId(destP.SnId)
			if destPs != nil && destPs.IsOnLine() {
				roleId := common.DefaultRoleId
				if p.Roles != nil {
					roleId = int(p.Roles.ModId)
				}
				pack.Friend = &friend.FriendInfo{
					SnId:     proto.Int32(p.SnId),
					Name:     proto.String(p.Name),
					Sex:      proto.Int32(p.Sex),
					Head:     proto.Int32(p.Head),
					HeadUrl:  proto.String(p.HeadUrl),
					CreateTs: proto.Int64(time.Now().Unix()),
					LogoutTs: proto.Int64(p.LastLogoutTime.Unix()),
					RoleId:   int32(roleId),
				}
				destPs.SendToClient(int(friend.FriendPacketID_PACKET_SCFriendOp), pack)
			}
		}
		logger.Logger.Tracef(">>FriendAgree %d -> %d, %v", p.SnId, destP.SnId, pack)
	}
	me := FriendMgrSington.GetPlayer(p.Platform, p.SnId)
	if me == nil {
		SendToClick(friend.OpResultCode_OPRC_Error)
		return
	}
	if FriendMgrSington.IsFriend(p.Platform, p.SnId, destP.SnId) { //已经是好友了
		SendToClick(friend.OpResultCode_OPRC_Friend_AlreadyAdd)
		return
	}
	//验证自己
	if len(me.BindFriend) >= FriendMaxNum {
		SendToClick(friend.OpResultCode_OPRC_Friend_FriendMax)
		return
	}

	destPFriend := FriendMgrSington.GetPlayer(destP.Platform, destP.SnId)
	if destPFriend != nil {
		if len(destPFriend.BindFriend) >= FriendMaxNum {
			SendToClick(friend.OpResultCode_OPRC_Friend_DestFriendMax)
			return
		}
	}

	var friendDB *model.Friend
	var err error
	task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
		if destPFriend == nil {
			// 不在线
			friendDB, err = model.QueryFriendBySnid(destP.Platform, destP.SnId)
			if err != nil {
				logger.Logger.Error("QueryFriendBySnid:", err, destP.SnId)
				return friend.OpResultCode_OPRC_Error
			}
			if friendDB == nil || friendDB.SnId != destP.SnId {
				friendDB = model.NewFriend(destP.Platform, destP.SnId)
			}

			if len(friendDB.BindFriend) >= FriendMaxNum {
				return friend.OpResultCode_OPRC_Friend_FriendMax
			}

			for _, v := range friendDB.BindFriend {
				if v.SnId == p.SnId {
					return friend.OpResultCode_OPRC_Friend_AlreadyAdd
				}
			}
		}

		ret, err := model.QueryFriendApplyBySnid(p.Platform, p.SnId)
		if err != nil {
			return friend.OpResultCode_OPRC_Error
		}
		//查看是否在申请列表
		if ret != nil {
			if ret.ApplySnids != nil {
				for i, as := range ret.ApplySnids {
					if as.SnId == destP.SnId {
						//在申请列表  删除
						ret.ApplySnids = append(ret.ApplySnids[:i], ret.ApplySnids[i+1:]...)
						model.UpsertFriendApply(p.Platform, p.SnId, ret)
						// 保存好友关系
						if friendDB != nil {
							friendDB.BindFriend = append(friendDB.BindFriend, &model.BindFriend{
								SnId:       p.SnId,
								CreateTime: time.Now().Unix(),
							})
							model.UpsertFriend(friendDB)
						}
						return nil
					}
				}
			}
		}
		return friend.OpResultCode_OPRC_Error
	}), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) {
		if data != nil {
			logger.Logger.Error("FriendAgree data:", data)
			SendToClick(data.(friend.OpResultCode))
			return
		}

		//同意者加入到被同意者好友里
		if destPlayer := FriendMgrSington.GetPlayer(destP.Platform, destP.SnId); destPlayer != nil {
			roleId := common.DefaultRoleId
			if p.Roles != nil {
				roleId = int(p.Roles.ModId)
			}
			result := FriendMgrSington.AgreeApply(destP.Platform, destP.SnId, &model.BindFriend{
				SnId:       p.SnId,
				CreateTime: time.Now().Unix(),
				Platform:   p.Platform,
				Name:       p.Name,
				Head:       p.Head,
				HeadUrl:    p.HeadUrl,
				Sex:        p.Sex,
				LogoutTime: p.LastLogoutTime.Unix(),
				RoleId:     int32(roleId),
			})
			if result != friend.OpResultCode_OPRC_Sucess && result != friend.OpResultCode_OPRC_Friend_AlreadyAdd {
				logger.Logger.Warn("AgreeApply error: ", result)
				SendToClick(result)
				return
			}
		}

		// 被同意者加入到同意者好友里
		result := FriendMgrSington.AgreeApply(p.Platform, p.SnId, destP)
		if result != friend.OpResultCode_OPRC_Sucess {
			SendToClick(result, false)
			SendToClick(result)
			return
		}
		SendToClick(friend.OpResultCode_OPRC_Sucess, false)
		SendToClick(friend.OpResultCode_OPRC_Sucess)
	})).StartByFixExecutor(FriendWrite)
}

func (this *FriendMgr) FriendRefuse(p *Player, destP *model.BindFriend) {
	SendToClick := func(retCode friend.OpResultCode, self ...bool) {
		pack := &friend.SCFriendOp{
			OpCode:    proto.Int32(OpType_Refuse),
			SnId:      proto.Int32(destP.SnId),
			OpRetCode: retCode,
		}
		if len(self) == 0 {
			p.SendToClient(int(friend.FriendPacketID_PACKET_SCFriendOp), pack)
		} else {
			destPs := PlayerMgrSington.GetPlayerBySnId(destP.SnId)
			if destPs != nil && destPs.IsOnLine() {
				destPs.SendToClient(int(friend.FriendPacketID_PACKET_SCFriendOp), pack)
			}
		}
		logger.Logger.Tracef(">>FriendRefuse %d -> %d, %v", p.SnId, destP.SnId, pack)
	}
	if FriendMgrSington.IsFriend(p.Platform, p.SnId, destP.SnId) {
		SendToClick(friend.OpResultCode_OPRC_Friend_AlreadyAdd)
		return
	}
	task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
		ret, _ := model.QueryFriendApplyBySnid(p.Platform, p.SnId)
		if ret != nil {
			if ret.ApplySnids != nil {
				for i, as := range ret.ApplySnids {
					if as.SnId == destP.SnId {
						ret.ApplySnids = append(ret.ApplySnids[:i], ret.ApplySnids[i+1:]...)
						model.UpsertFriendApply(p.Platform, p.SnId, ret)
						return nil
					}
				}
			}
		}
		return errors.New("not in apply")
	}), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) {
		//拒绝了不提醒
		if data != nil {
			logger.Logger.Error("FriendRefuse data:", data)
			SendToClick(friend.OpResultCode_OPRC_Error)
			return
		}
		SendToClick(friend.OpResultCode_OPRC_Sucess)
	})).StartByFixExecutor(FriendWrite)
}

func (this *FriendMgr) FriendDelete(p *Player, destP *model.BindFriend) {
	SendToClick := func(retCode friend.OpResultCode, self ...bool) {
		pack := &friend.SCFriendOp{
			OpCode:    proto.Int32(OpType_Delete),
			SnId:      proto.Int32(destP.SnId),
			OpRetCode: retCode,
		}
		if len(self) == 0 {
			pack.SnId = destP.SnId
			p.SendToClient(int(friend.FriendPacketID_PACKET_SCFriendOp), pack)
			logger.Logger.Tracef(">>FriendDelete %d -> %d, %v", p.SnId, destP.SnId, pack)
		} else {
			destPs := PlayerMgrSington.GetPlayerBySnId(destP.SnId)
			if destPs != nil && destPs.IsOnLine() {
				pack.SnId = p.SnId
				destPs.SendToClient(int(friend.FriendPacketID_PACKET_SCFriendOp), pack)
				logger.Logger.Tracef(">>FriendDelete dest %d -> %d, %v", p.SnId, destP.SnId, pack)
			}
		}
	}
	f := FriendMgrSington.GetPlayer(p.Platform, destP.SnId)
	if f != nil {
		//发起者删除被删除者
		isok1 := FriendMgrSington.RemoveFriend(p.Platform, p.SnId, f.SnId)
		if !isok1 {
			logger.Logger.Warn("RemoveFriend error: ", p.SnId, " del friend:", f.SnId)
			SendToClick(friend.OpResultCode_OPRC_Error)
			return
		}
		//被删除者删除发起者
		isok2 := FriendMgrSington.RemoveFriend(f.Platform, f.SnId, p.SnId)
		if !isok2 {
			logger.Logger.Warn("RemoveFriend error: ", f.SnId, " del friend:", p.SnId)
			//删除失败不用通知
		} else {
			SendToClick(friend.OpResultCode_OPRC_Sucess, false)
		}
		SendToClick(friend.OpResultCode_OPRC_Sucess)
		ChatMgrSington.DelChat(p.Platform, p.SnId, f.SnId)
	} else {
		task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
			ret, _ := model.QueryFriendBySnid(p.Platform, destP.SnId)
			if ret != nil {
				//被删除者删除发起者
				if ret.BindFriend != nil {
					for i, bindFriend := range ret.BindFriend {
						if bindFriend.SnId == p.SnId {
							ret.BindFriend = append(ret.BindFriend[:i], ret.BindFriend[i+1:]...)
							model.UpsertFriend(ret)
							SendToClick(friend.OpResultCode_OPRC_Sucess, false)
							return nil
						}
					}
				}
			}
			return errors.New("not in apply")
		}), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) {
			if data != nil {
				logger.Logger.Error("FriendDelete data:", data)
				SendToClick(friend.OpResultCode_OPRC_Error)
				return
			} else {
				//发起者删除被删除者
				isok1 := FriendMgrSington.RemoveFriend(p.Platform, p.SnId, destP.SnId)
				if !isok1 {
					logger.Logger.Warn("RemoveFriend error: ", p.SnId, " del friend:", destP.SnId)
					//SendToClick(friend.OpResultCode_OPRC_Error)
					//return
				}
				SendToClick(friend.OpResultCode_OPRC_Sucess)
			}
			ChatMgrSington.DelChat(p.Platform, p.SnId, destP.SnId)
		})).StartByFixExecutor(FriendWrite)
	}
}

func (this *FriendMgr) Update() {
}

func (this *FriendMgr) Shutdown() {
	module.UnregisteModule(this)
}

func (this *FriendMgr) UpdateName(snId int32, name string) {
	p := PlayerMgrSington.GetPlayerBySnId(snId)
	if p != nil {
		this.UpdateInfo(p.Platform, p.SnId)
	}
}

func (this *FriendMgr) UpdateHead(snId, head int32) {
	p := PlayerMgrSington.GetPlayerBySnId(snId)
	if p != nil {
		this.UpdateInfo(p.Platform, p.SnId)
	}
}

//========================implement IPlayerLoad ==============================

func (this *FriendMgr) Load(platform string, snid int32, player any) *internal.PlayerLoadReplay {
	return nil
}

func (this *FriendMgr) Callback(player any, ret *internal.PlayerLoadReplay) {
}

func (this *FriendMgr) LoadAfter(platform string, snid int32) *internal.PlayerLoadReplay {
	ret := &internal.PlayerLoadReplay{
		Platform: platform,
		Snid:     snid,
	}

	friendDB, err := model.QueryFriendBySnid(platform, snid)
	if err != nil {
		logger.Logger.Error("QueryFriendBySnid:", err, snid)
		ret.Err = err
		return ret
	}

	if friendDB == nil {
		return ret
	}

	var offSnID []int32
	for _, v := range friendDB.BindFriend {
		roleId := common.DefaultRoleId
		if v.RoleId != 0 {
			roleId = int(v.RoleId)
		}
		p := PlayerMgrSington.GetPlayerBySnId(v.SnId)
		if p != nil {
			v.Platform = p.Platform
			v.Name = p.Name
			v.Head = p.Head
			v.HeadUrl = p.HeadUrl
			v.Sex = p.Sex
			if !p.IsOnLine() {
				v.LogoutTime = p.LastLogoutTime.Unix()
			}
			v.RoleId = int32(roleId)
		} else {
			offSnID = append(offSnID, v.SnId)
		}
	}

	if len(offSnID) > 0 {
		offFriends, err := model.QueryFriendsBySnids(platform, offSnID)
		if err != nil {
			logger.Logger.Error("QueryFriendsBySnids is err:", err)
			ret.Err = err
			return ret
		}
		for _, offFriend := range offFriends {
			for _, v := range friendDB.BindFriend {
				roleId := common.DefaultRoleId
				if v.RoleId != 0 {
					roleId = int(v.RoleId)
				}
				if v.SnId == offFriend.SnId {
					v.Platform = offFriend.Platform
					v.Name = offFriend.Name
					v.Head = offFriend.Head
					v.HeadUrl = offFriend.HeadUrl
					v.Sex = offFriend.Sex
					v.LogoutTime = offFriend.LogoutTime
					v.RoleId = int32(roleId)
					break
				}
			}
		}
	}

	ret.Data = friendDB

	return ret
}

func (this *FriendMgr) CallbackAfter(ret *internal.PlayerLoadReplay) {
	if ret == nil || ret.Platform == "" || ret.Snid <= 0 {
		return
	}

	if ret.Err != nil {
		logger.Logger.Error("(this *FriendMgr) CallbackAfter err:", ret.Err)
		return
	}
	if ret.Data == nil {
		this.Add(ret.Platform, ret.Snid)
		logger.Logger.Infof("(this *FriendMgr) LoadFriendData New: %+v", *this.GetPlayer(ret.Platform, ret.Snid))
		return
	}

	data, ok := ret.Data.(*model.Friend)
	if !ok {
		logger.Logger.Error("bug")
		return
	}

	if this.FriendList[ret.Platform] == nil {
		this.FriendList[ret.Platform] = make(map[int32]*model.Friend)
	}

	this.FriendList[ret.Platform][ret.Snid] = data
	logger.Logger.Infof("(this *FriendMgr) LoadFriendData db: %+v", *data)
	p := PlayerMgrSington.GetPlayerBySnId(ret.Snid)
	if p != nil {
		this.UpdateInfo(ret.Platform, ret.Snid)
	}
	FriendUnreadMgrSington.LoadFriendUnreadData(ret.Platform, ret.Snid)
	this.ApplyList(ret.Platform, ret.Snid)
}

func (this *FriendMgr) Save(platform string, snid int32, isSync, force bool) {
	logger.Logger.Trace("(this *FriendMgr) SaveFriendData ", snid)
	ret := this.GetPlayer(platform, snid)
	if ret == nil || (!ret.Dirty && !force) {
		return
	}

	var res bool
	f := func() {
		res = model.UpsertFriend(ret) != nil
	}
	cf := func() {
		if res {
			ret.Dirty = false
		}
	}

	if isSync {
		f()
		cf()
		return
	}

	task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
		f()
		return nil
	}), task.CompleteNotifyWrapper(func(i interface{}, t task.Task) {
		cf()
	})).StartByFixExecutor(fmt.Sprintf("player%v", ret.SnId))
}

func (this *FriendMgr) Release(platform string, snid int32) {
	this.Del(platform, snid)
}

func init() {
	module.RegisteModule(FriendMgrSington, time.Hour, 0)
	internal.RegisterPlayerLoad(FriendMgrSington)
}
