// Copyright © 2023 OpenIM SDK. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package conversation_msg

import (
	"context"
	"errors"
	"github.com/openimsdk/openim-sdk-core/v3/internal/util"
	"github.com/openimsdk/openim-sdk-core/v3/pkg/common"
	"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
	"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
	"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
	"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
	"github.com/openimsdk/tools/utils/timeutil"

	"github.com/jinzhu/copier"
	pbMsg "github.com/openimsdk/protocol/msg"
	"github.com/openimsdk/protocol/sdkws"
	"github.com/openimsdk/tools/log"
)

func (c *Conversation) doRevokeMsg(ctx context.Context, msg *sdkws.MsgData) {
	var tips sdkws.RevokeMsgTips
	if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
		log.ZError(ctx, "unmarshal failed", err, "msg", msg)
		return
	}
	log.ZDebug(ctx, "do revokeMessage", "tips", &tips)
	c.revokeMessage(ctx, &tips)
}

func (c *Conversation) revokeMessage(ctx context.Context, tips *sdkws.RevokeMsgTips) {
	revokedMsg, err := c.db.GetMessageBySeq(ctx, tips.ConversationID, tips.Seq)
	if err != nil {
		log.ZError(ctx, "GetMessageBySeq failed", err, "tips", &tips)
		return
	}
	var revokerRole int32
	var revokerNickname string
	if tips.IsAdminRevoke || tips.SesstionType == constant.SingleChatType {
		_, userName, err := c.getUserNameAndFaceURL(ctx, tips.RevokerUserID)
		if err != nil {
			log.ZError(ctx, "GetUserNameAndFaceURL failed", err, "tips", &tips)
		} else {
			log.ZDebug(ctx, "revoker user name", "userName", userName)
		}
		revokerNickname = userName
	} else if tips.SesstionType == constant.SuperGroupChatType {
		conversation, err := c.db.GetConversation(ctx, tips.ConversationID)
		if err != nil {
			log.ZError(ctx, "GetConversation failed", err, "conversationID", tips.ConversationID)
			return
		}
		groupMember, err := c.db.GetGroupMemberInfoByGroupIDUserID(ctx, conversation.GroupID, tips.RevokerUserID)
		if err != nil {
			log.ZError(ctx, "GetGroupMemberInfoByGroupIDUserID failed", err, "tips", &tips)
		} else {
			log.ZDebug(ctx, "revoker member name", "groupMember", groupMember)
			revokerRole = groupMember.RoleLevel
			revokerNickname = groupMember.Nickname
		}
	}
	m := sdk_struct.MessageRevoked{
		RevokerID:                   tips.RevokerUserID,
		RevokerRole:                 revokerRole,
		ClientMsgID:                 revokedMsg.ClientMsgID,
		RevokerNickname:             revokerNickname,
		RevokeTime:                  tips.RevokeTime,
		SourceMessageSendTime:       revokedMsg.SendTime,
		SourceMessageSendID:         revokedMsg.SendID,
		SourceMessageSenderNickname: revokedMsg.SenderNickname,
		SessionType:                 tips.SesstionType,
		Seq:                         tips.Seq,
		Ex:                          revokedMsg.Ex,
		IsAdminRevoke:               tips.IsAdminRevoke,
	}
	// log.ZDebug(ctx, "callback revokeMessage", "m", m)
	var n sdk_struct.NotificationElem
	n.Detail = utils.StructToJsonString(m)
	if err := c.db.UpdateMessageBySeq(ctx, tips.ConversationID, &model_struct.LocalChatLog{Seq: tips.Seq,
		Content: utils.StructToJsonString(n), ContentType: constant.RevokeNotification}); err != nil {
		log.ZError(ctx, "UpdateMessageBySeq failed", err, "tips", &tips)
		return
	}
	conversation, err := c.db.GetConversation(ctx, tips.ConversationID)
	if err != nil {
		log.ZError(ctx, "GetConversation failed", err, "tips", &tips)
		return
	}
	var latestMsg sdk_struct.MsgStruct
	utils.JsonStringToStruct(conversation.LatestMsg, &latestMsg)
	log.ZDebug(ctx, "latestMsg", "latestMsg", &latestMsg, "seq", tips.Seq)
	if latestMsg.Seq <= tips.Seq {
		var newLatesetMsg sdk_struct.MsgStruct
		msgs, err := c.db.GetMessageListNoTime(ctx, tips.ConversationID, 1, false)
		if err != nil || len(msgs) == 0 {
			log.ZError(ctx, "GetMessageListNoTime failed", err, "tips", &tips)
			return
		}
		log.ZDebug(ctx, "latestMsg is revoked", "seq", tips.Seq, "msg", msgs[0])
		copier.Copy(&newLatesetMsg, msgs[0])
		err = c.msgConvert(&newLatesetMsg)
		if err != nil {
			log.ZError(ctx, "parsing data error", err, latestMsg)
		} else {
			log.ZDebug(ctx, "revoke update conversatoin", "msg", utils.StructToJsonString(newLatesetMsg))
			if err := c.db.UpdateColumnsConversation(ctx, tips.ConversationID, map[string]interface{}{"latest_msg": utils.StructToJsonString(newLatesetMsg),
				"latest_msg_send_time": newLatesetMsg.SendTime}); err != nil {
				log.ZError(ctx, "UpdateColumnsConversation failed", err, "newLatesetMsg", newLatesetMsg)
			} else {
				c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: []string{tips.ConversationID}}})
			}
		}
	}
	c.msgListener().OnNewRecvMessageRevoked(utils.StructToJsonString(m))
	msgList, err := c.db.SearchAllMessageByContentType(ctx, conversation.ConversationID, constant.Quote)
	if err != nil {
		log.ZError(ctx, "SearchAllMessageByContentType failed", err, "tips", &tips)
		return
	}
	for _, v := range msgList {
		c.quoteMsgRevokeHandle(ctx, tips.ConversationID, v, m)
	}
}

func (c *Conversation) quoteMsgRevokeHandle(ctx context.Context, conversationID string, v *model_struct.LocalChatLog, revokedMsg sdk_struct.MessageRevoked) {
	s := sdk_struct.MsgStruct{}
	_ = utils.JsonStringToStruct(v.Content, &s.QuoteElem)

	if s.QuoteElem.QuoteMessage == nil {
		return
	}
	if s.QuoteElem.QuoteMessage.ClientMsgID != revokedMsg.ClientMsgID {
		return
	}
	s.QuoteElem.QuoteMessage.Content = utils.StructToJsonString(revokedMsg)
	s.QuoteElem.QuoteMessage.ContentType = constant.RevokeNotification
	v.Content = utils.StructToJsonString(s.QuoteElem)
	if err := c.db.UpdateMessageBySeq(ctx, conversationID, v); err != nil {
		log.ZError(ctx, "UpdateMessage failed", err, "v", v)
	}
}

func (c *Conversation) revokeOneMessage(ctx context.Context, conversationID, clientMsgID string) error {
	conversation, err := c.db.GetConversation(ctx, conversationID)
	if err != nil {
		return err
	}
	message, err := c.db.GetMessage(ctx, conversationID, clientMsgID)
	if err != nil {
		return err
	}
	if message.Status != constant.MsgStatusSendSuccess {
		return errors.New("only send success message can be revoked")
	}
	switch conversation.ConversationType {
	case constant.SingleChatType:
		if message.SendID != c.loginUserID {
			return errors.New("only send by yourself message can be revoked")
		}
	case constant.SuperGroupChatType:
		if message.SendID != c.loginUserID {
			groupAdmins, err := c.db.GetGroupMemberOwnerAndAdminDB(ctx, conversation.GroupID)
			if err != nil {
				return err
			}
			var isAdmin bool
			for _, member := range groupAdmins {
				if member.UserID == c.loginUserID {
					isAdmin = true
					break
				}
			}
			if !isAdmin {
				return errors.New("only group admin can revoke message")
			}
		}
	}
	if err := util.ApiPost(ctx, constant.RevokeMsgRouter, pbMsg.RevokeMsgReq{ConversationID: conversationID, Seq: message.Seq, UserID: c.loginUserID}, nil); err != nil {
		return err
	}
	c.revokeMessage(ctx, &sdkws.RevokeMsgTips{
		ConversationID: conversationID,
		Seq:            message.Seq,
		RevokerUserID:  c.loginUserID,
		RevokeTime:     timeutil.GetCurrentTimestampBySecond(),
		SesstionType:   conversation.ConversationType,
		ClientMsgID:    clientMsgID,
	})
	return nil
}
