// Copyright © 2023 OpenIM. 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 group

import (
	"context"
	"time"

	"git.imall.cloud/openim/open-im-server-deploy/pkg/apistruct"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/callbackstruct"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/common/webhook"
	"git.imall.cloud/openim/protocol/constant"
	"git.imall.cloud/openim/protocol/group"
	"git.imall.cloud/openim/protocol/wrapperspb"
	"github.com/openimsdk/tools/log"
	"github.com/openimsdk/tools/mcontext"
	"github.com/openimsdk/tools/utils/datautil"
)

// CallbackBeforeCreateGroup callback before create group.
func (g *groupServer) webhookBeforeCreateGroup(ctx context.Context, before *config.BeforeConfig, req *group.CreateGroupReq) error {
	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
		cbReq := &callbackstruct.CallbackBeforeCreateGroupReq{
			CallbackCommand: callbackstruct.CallbackBeforeCreateGroupCommand,
			OperationID:     mcontext.GetOperationID(ctx),
			GroupInfo:       req.GroupInfo,
		}
		cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{
			UserID:    req.OwnerUserID,
			RoleLevel: constant.GroupOwner,
		})
		for _, userID := range req.AdminUserIDs {
			cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{
				UserID:    userID,
				RoleLevel: constant.GroupAdmin,
			})
		}
		for _, userID := range req.MemberUserIDs {
			cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{
				UserID:    userID,
				RoleLevel: constant.GroupOrdinaryUsers,
			})
		}
		resp := &callbackstruct.CallbackBeforeCreateGroupResp{}

		if err := g.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
			return err
		}

		datautil.NotNilReplace(&req.GroupInfo.GroupID, resp.GroupID)
		datautil.NotNilReplace(&req.GroupInfo.GroupName, resp.GroupName)
		datautil.NotNilReplace(&req.GroupInfo.Notification, resp.Notification)
		datautil.NotNilReplace(&req.GroupInfo.Introduction, resp.Introduction)
		datautil.NotNilReplace(&req.GroupInfo.FaceURL, resp.FaceURL)
		datautil.NotNilReplace(&req.GroupInfo.OwnerUserID, resp.OwnerUserID)
		datautil.NotNilReplace(&req.GroupInfo.Ex, resp.Ex)
		datautil.NotNilReplace(&req.GroupInfo.Status, resp.Status)
		datautil.NotNilReplace(&req.GroupInfo.CreatorUserID, resp.CreatorUserID)
		datautil.NotNilReplace(&req.GroupInfo.GroupType, resp.GroupType)
		datautil.NotNilReplace(&req.GroupInfo.NeedVerification, resp.NeedVerification)
		datautil.NotNilReplace(&req.GroupInfo.LookMemberInfo, resp.LookMemberInfo)
		return nil
	})
}

func (g *groupServer) webhookAfterCreateGroup(ctx context.Context, after *config.AfterConfig, req *group.CreateGroupReq) {
	cbReq := &callbackstruct.CallbackAfterCreateGroupReq{
		CallbackCommand: callbackstruct.CallbackAfterCreateGroupCommand,
		GroupInfo:       req.GroupInfo,
	}
	cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{
		UserID:    req.OwnerUserID,
		RoleLevel: constant.GroupOwner,
	})
	for _, userID := range req.AdminUserIDs {
		cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{
			UserID:    userID,
			RoleLevel: constant.GroupAdmin,
		})
	}
	for _, userID := range req.MemberUserIDs {
		cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{
			UserID:    userID,
			RoleLevel: constant.GroupOrdinaryUsers,
		})
	}
	g.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterCreateGroupResp{}, after)
}

func (g *groupServer) webhookBeforeMembersJoinGroup(ctx context.Context, before *config.BeforeConfig, groupMembers []*model.GroupMember, groupID string, groupEx string) error {
	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
		groupMembersMap := datautil.SliceToMap(groupMembers, func(e *model.GroupMember) string {
			return e.UserID
		})
		var groupMembersCallback []*callbackstruct.CallbackGroupMember

		for _, member := range groupMembers {
			groupMembersCallback = append(groupMembersCallback, &callbackstruct.CallbackGroupMember{
				UserID: member.UserID,
				Ex:     member.Ex,
			})
		}

		cbReq := &callbackstruct.CallbackBeforeMembersJoinGroupReq{
			CallbackCommand: callbackstruct.CallbackBeforeMembersJoinGroupCommand,
			GroupID:         groupID,
			MembersList:     groupMembersCallback,
			GroupEx:         groupEx,
		}
		resp := &callbackstruct.CallbackBeforeMembersJoinGroupResp{}

		if err := g.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
			return err
		}
		
		// 记录webhook响应，用于排查自动禁言问题
		if len(resp.MemberCallbackList) > 0 {
			log.ZInfo(ctx, "webhookBeforeMembersJoinGroup: webhook response received", 
				"groupID", groupID, 
				"memberCallbackListCount", len(resp.MemberCallbackList),
				"memberCallbackList", resp.MemberCallbackList)
		}

		for _, memberCallbackResp := range resp.MemberCallbackList {
			if _, ok := groupMembersMap[(*memberCallbackResp.UserID)]; ok {
				if memberCallbackResp.MuteEndTime != nil {
					muteEndTimeTimestamp := *memberCallbackResp.MuteEndTime
					now := time.Now()
					nowUnixMilli := now.UnixMilli()
					
					// 检查时间戳是否合理（防止时间戳单位错误导致自动禁言）
					// 如果时间戳小于1000000000000（2001-09-09），可能是秒级时间戳，需要转换为毫秒
					// 如果时间戳大于当前时间+10年，可能是时间戳单位错误
					var muteEndTime time.Time
					if muteEndTimeTimestamp < 1000000000000 {
						// 可能是秒级时间戳，转换为毫秒
						log.ZWarn(ctx, "webhookBeforeMembersJoinGroup: MuteEndTime appears to be in seconds, converting to milliseconds", nil,
							"groupID", groupID, 
							"userID", *memberCallbackResp.UserID, 
							"originalTimestamp", muteEndTimeTimestamp)
						muteEndTime = time.Unix(muteEndTimeTimestamp, 0)
					} else if muteEndTimeTimestamp > nowUnixMilli+10*365*24*3600*1000 {
						// 时间戳超过当前时间10年，可能是单位错误，忽略
						log.ZWarn(ctx, "webhookBeforeMembersJoinGroup: MuteEndTime is too far in the future, ignoring", nil,
							"groupID", groupID, 
							"userID", *memberCallbackResp.UserID, 
							"muteEndTimeTimestamp", muteEndTimeTimestamp,
							"nowUnixMilli", nowUnixMilli)
						continue
					} else {
						muteEndTime = time.UnixMilli(muteEndTimeTimestamp)
					}
					
					// 记录webhook返回的禁言时间，用于排查自动禁言问题
					log.ZInfo(ctx, "webhookBeforeMembersJoinGroup: webhook returned MuteEndTime", 
						"groupID", groupID, 
						"userID", *memberCallbackResp.UserID, 
						"muteEndTimeTimestamp", muteEndTimeTimestamp,
						"muteEndTime", muteEndTime.Format(time.RFC3339),
						"now", now.Format(time.RFC3339),
						"isMuted", muteEndTime.After(now),
						"mutedDurationSeconds", muteEndTime.Sub(now).Seconds())
					groupMembersMap[(*memberCallbackResp.UserID)].MuteEndTime = muteEndTime
				}

				datautil.NotNilReplace(&groupMembersMap[(*memberCallbackResp.UserID)].FaceURL, memberCallbackResp.FaceURL)
				datautil.NotNilReplace(&groupMembersMap[(*memberCallbackResp.UserID)].Ex, memberCallbackResp.Ex)
				datautil.NotNilReplace(&groupMembersMap[(*memberCallbackResp.UserID)].Nickname, memberCallbackResp.Nickname)
				datautil.NotNilReplace(&groupMembersMap[(*memberCallbackResp.UserID)].RoleLevel, memberCallbackResp.RoleLevel)
			}
		}

		return nil
	})
}

func (g *groupServer) webhookBeforeSetGroupMemberInfo(ctx context.Context, before *config.BeforeConfig, req *group.SetGroupMemberInfo) error {
	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
		cbReq := callbackstruct.CallbackBeforeSetGroupMemberInfoReq{
			CallbackCommand: callbackstruct.CallbackBeforeSetGroupMemberInfoCommand,
			GroupID:         req.GroupID,
			UserID:          req.UserID,
		}
		if req.Nickname != nil {
			cbReq.Nickname = &req.Nickname.Value
		}
		if req.FaceURL != nil {
			cbReq.FaceURL = &req.FaceURL.Value
		}
		if req.RoleLevel != nil {
			cbReq.RoleLevel = &req.RoleLevel.Value
		}
		if req.Ex != nil {
			cbReq.Ex = &req.Ex.Value
		}
		resp := &callbackstruct.CallbackBeforeSetGroupMemberInfoResp{}
		if err := g.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
			return err
		}
		if resp.FaceURL != nil {
			req.FaceURL = wrapperspb.String(*resp.FaceURL)
		}
		if resp.Nickname != nil {
			req.Nickname = wrapperspb.String(*resp.Nickname)
		}
		if resp.RoleLevel != nil {
			req.RoleLevel = wrapperspb.Int32(*resp.RoleLevel)
		}
		if resp.Ex != nil {
			req.Ex = wrapperspb.String(*resp.Ex)
		}
		return nil
	})
}

func (g *groupServer) webhookAfterSetGroupMemberInfo(ctx context.Context, after *config.AfterConfig, req *group.SetGroupMemberInfo) {
	cbReq := callbackstruct.CallbackAfterSetGroupMemberInfoReq{
		CallbackCommand: callbackstruct.CallbackAfterSetGroupMemberInfoCommand,
		GroupID:         req.GroupID,
		UserID:          req.UserID,
	}
	if req.Nickname != nil {
		cbReq.Nickname = &req.Nickname.Value
	}
	if req.FaceURL != nil {
		cbReq.FaceURL = &req.FaceURL.Value
	}
	if req.RoleLevel != nil {
		cbReq.RoleLevel = &req.RoleLevel.Value
	}
	if req.Ex != nil {
		cbReq.Ex = &req.Ex.Value
	}
	g.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterSetGroupMemberInfoResp{}, after)
}

func (g *groupServer) webhookAfterQuitGroup(ctx context.Context, after *config.AfterConfig, req *group.QuitGroupReq) {
	cbReq := &callbackstruct.CallbackQuitGroupReq{
		CallbackCommand: callbackstruct.CallbackAfterQuitGroupCommand,
		GroupID:         req.GroupID,
		UserID:          req.UserID,
	}
	g.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackQuitGroupResp{}, after)
}

func (g *groupServer) webhookAfterKickGroupMember(ctx context.Context, after *config.AfterConfig, req *group.KickGroupMemberReq) {
	cbReq := &callbackstruct.CallbackKillGroupMemberReq{
		CallbackCommand: callbackstruct.CallbackAfterKickGroupCommand,
		GroupID:         req.GroupID,
		KickedUserIDs:   req.KickedUserIDs,
		Reason:          req.Reason,
	}
	g.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackKillGroupMemberResp{}, after)
}

func (g *groupServer) webhookAfterDismissGroup(ctx context.Context, after *config.AfterConfig, req *callbackstruct.CallbackDisMissGroupReq) {
	req.CallbackCommand = callbackstruct.CallbackAfterDisMissGroupCommand
	g.webhookClient.AsyncPost(ctx, req.GetCallbackCommand(), req, &callbackstruct.CallbackDisMissGroupResp{}, after)
}

func (g *groupServer) webhookBeforeApplyJoinGroup(ctx context.Context, before *config.BeforeConfig, req *callbackstruct.CallbackJoinGroupReq) (err error) {
	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
		req.CallbackCommand = callbackstruct.CallbackBeforeJoinGroupCommand
		resp := &callbackstruct.CallbackJoinGroupResp{}
		if err := g.webhookClient.SyncPost(ctx, req.GetCallbackCommand(), req, resp, before); err != nil {
			return err
		}
		return nil
	})
}

func (g *groupServer) webhookAfterTransferGroupOwner(ctx context.Context, after *config.AfterConfig, req *group.TransferGroupOwnerReq) {
	cbReq := &callbackstruct.CallbackTransferGroupOwnerReq{
		CallbackCommand: callbackstruct.CallbackAfterTransferGroupOwnerCommand,
		GroupID:         req.GroupID,
		OldOwnerUserID:  req.OldOwnerUserID,
		NewOwnerUserID:  req.NewOwnerUserID,
	}
	g.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackTransferGroupOwnerResp{}, after)
}

func (g *groupServer) webhookBeforeInviteUserToGroup(ctx context.Context, before *config.BeforeConfig, req *group.InviteUserToGroupReq) (err error) {
	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
		cbReq := &callbackstruct.CallbackBeforeInviteUserToGroupReq{
			CallbackCommand: callbackstruct.CallbackBeforeInviteJoinGroupCommand,
			OperationID:     mcontext.GetOperationID(ctx),
			GroupID:         req.GroupID,
			Reason:          req.Reason,
			InvitedUserIDs:  req.InvitedUserIDs,
		}

		resp := &callbackstruct.CallbackBeforeInviteUserToGroupResp{}
		if err := g.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
			return err
		}

		// Handle the scenario where certain members are refused
		// You might want to update the req.Members list or handle it as per your business logic

		// if len(resp.RefusedMembersAccount) > 0 {
		// implement members are refused
		// }

		return nil
	})
}

func (g *groupServer) webhookAfterJoinGroup(ctx context.Context, after *config.AfterConfig, req *group.JoinGroupReq) {
	cbReq := &callbackstruct.CallbackAfterJoinGroupReq{
		CallbackCommand: callbackstruct.CallbackAfterJoinGroupCommand,
		OperationID:     mcontext.GetOperationID(ctx),
		GroupID:         req.GroupID,
		ReqMessage:      req.ReqMessage,
		JoinSource:      req.JoinSource,
		InviterUserID:   req.InviterUserID,
	}
	g.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterJoinGroupResp{}, after)
}

func (g *groupServer) webhookBeforeSetGroupInfo(ctx context.Context, before *config.BeforeConfig, req *group.SetGroupInfoReq) error {
	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
		cbReq := &callbackstruct.CallbackBeforeSetGroupInfoReq{
			CallbackCommand: callbackstruct.CallbackBeforeSetGroupInfoCommand,
			GroupID:         req.GroupInfoForSet.GroupID,
			Notification:    req.GroupInfoForSet.Notification,
			Introduction:    req.GroupInfoForSet.Introduction,
			FaceURL:         req.GroupInfoForSet.FaceURL,
			GroupName:       req.GroupInfoForSet.GroupName,
		}
		if req.GroupInfoForSet.Ex != nil {
			cbReq.Ex = req.GroupInfoForSet.Ex.Value
		}
		log.ZDebug(ctx, "debug CallbackBeforeSetGroupInfo", "ex", cbReq.Ex)
		if req.GroupInfoForSet.NeedVerification != nil {
			cbReq.NeedVerification = req.GroupInfoForSet.NeedVerification.Value
		}
		if req.GroupInfoForSet.LookMemberInfo != nil {
			cbReq.LookMemberInfo = req.GroupInfoForSet.LookMemberInfo.Value
		}
		if req.GroupInfoForSet.ApplyMemberFriend != nil {
			cbReq.ApplyMemberFriend = req.GroupInfoForSet.ApplyMemberFriend.Value
		}
		resp := &callbackstruct.CallbackBeforeSetGroupInfoResp{}

		if err := g.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
			return err
		}

		if resp.Ex != nil {
			req.GroupInfoForSet.Ex = wrapperspb.String(*resp.Ex)
		}
		if resp.NeedVerification != nil {
			req.GroupInfoForSet.NeedVerification = wrapperspb.Int32(*resp.NeedVerification)
		}
		if resp.LookMemberInfo != nil {
			req.GroupInfoForSet.LookMemberInfo = wrapperspb.Int32(*resp.LookMemberInfo)
		}
		if resp.ApplyMemberFriend != nil {
			req.GroupInfoForSet.ApplyMemberFriend = wrapperspb.Int32(*resp.ApplyMemberFriend)
		}
		datautil.NotNilReplace(&req.GroupInfoForSet.GroupID, &resp.GroupID)
		datautil.NotNilReplace(&req.GroupInfoForSet.GroupName, &resp.GroupName)
		datautil.NotNilReplace(&req.GroupInfoForSet.FaceURL, &resp.FaceURL)
		datautil.NotNilReplace(&req.GroupInfoForSet.Introduction, &resp.Introduction)
		return nil
	})
}

func (g *groupServer) webhookAfterSetGroupInfo(ctx context.Context, after *config.AfterConfig, req *group.SetGroupInfoReq) {
	cbReq := &callbackstruct.CallbackAfterSetGroupInfoReq{
		CallbackCommand: callbackstruct.CallbackAfterSetGroupInfoCommand,
		GroupID:         req.GroupInfoForSet.GroupID,
		Notification:    req.GroupInfoForSet.Notification,
		Introduction:    req.GroupInfoForSet.Introduction,
		FaceURL:         req.GroupInfoForSet.FaceURL,
		GroupName:       req.GroupInfoForSet.GroupName,
	}
	if req.GroupInfoForSet.Ex != nil {
		cbReq.Ex = &req.GroupInfoForSet.Ex.Value
	}
	if req.GroupInfoForSet.NeedVerification != nil {
		cbReq.NeedVerification = &req.GroupInfoForSet.NeedVerification.Value
	}
	if req.GroupInfoForSet.LookMemberInfo != nil {
		cbReq.LookMemberInfo = &req.GroupInfoForSet.LookMemberInfo.Value
	}
	if req.GroupInfoForSet.ApplyMemberFriend != nil {
		cbReq.ApplyMemberFriend = &req.GroupInfoForSet.ApplyMemberFriend.Value
	}
	g.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterSetGroupInfoResp{}, after)
}

func (g *groupServer) webhookBeforeSetGroupInfoEx(ctx context.Context, before *config.BeforeConfig, req *group.SetGroupInfoExReq) error {
	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
		cbReq := &callbackstruct.CallbackBeforeSetGroupInfoExReq{
			CallbackCommand: callbackstruct.CallbackBeforeSetGroupInfoExCommand,
			GroupID:         req.GroupID,
			GroupName:       req.GroupName,
			Notification:    req.Notification,
			Introduction:    req.Introduction,
			FaceURL:         req.FaceURL,
		}

		if req.Ex != nil {
			cbReq.Ex = req.Ex
		}
		log.ZDebug(ctx, "debug CallbackBeforeSetGroupInfoEx", "ex", cbReq.Ex)

		if req.NeedVerification != nil {
			cbReq.NeedVerification = req.NeedVerification
		}
		if req.LookMemberInfo != nil {
			cbReq.LookMemberInfo = req.LookMemberInfo
		}
		if req.ApplyMemberFriend != nil {
			cbReq.ApplyMemberFriend = req.ApplyMemberFriend
		}

		resp := &callbackstruct.CallbackBeforeSetGroupInfoExResp{}

		if err := g.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
			return err
		}

		datautil.NotNilReplace(&req.GroupID, &resp.GroupID)
		datautil.NotNilReplace(&req.GroupName, &resp.GroupName)
		datautil.NotNilReplace(&req.FaceURL, &resp.FaceURL)
		datautil.NotNilReplace(&req.Introduction, &resp.Introduction)
		datautil.NotNilReplace(&req.Ex, &resp.Ex)
		datautil.NotNilReplace(&req.NeedVerification, &resp.NeedVerification)
		datautil.NotNilReplace(&req.LookMemberInfo, &resp.LookMemberInfo)
		datautil.NotNilReplace(&req.ApplyMemberFriend, &resp.ApplyMemberFriend)

		return nil
	})
}

func (g *groupServer) webhookAfterSetGroupInfoEx(ctx context.Context, after *config.AfterConfig, req *group.SetGroupInfoExReq) {
	cbReq := &callbackstruct.CallbackAfterSetGroupInfoExReq{
		CallbackCommand: callbackstruct.CallbackAfterSetGroupInfoExCommand,
		GroupID:         req.GroupID,
		GroupName:       req.GroupName,
		Notification:    req.Notification,
		Introduction:    req.Introduction,
		FaceURL:         req.FaceURL,
	}

	if req.Ex != nil {
		cbReq.Ex = req.Ex
	}
	if req.NeedVerification != nil {
		cbReq.NeedVerification = req.NeedVerification
	}
	if req.LookMemberInfo != nil {
		cbReq.LookMemberInfo = req.LookMemberInfo
	}
	if req.ApplyMemberFriend != nil {
		cbReq.ApplyMemberFriend = req.ApplyMemberFriend
	}

	g.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterSetGroupInfoExResp{}, after)
}
