package v1

import (
	"context"
	"encoding/json"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/pingcap-inc/pchub/internal/pkg/rstore"
	"github.com/pingcap-inc/pchub/pkg/rstorage"

	"github.com/pingcap-inc/pchub/internal/apiserver/store"
	"github.com/pingcap-inc/pchub/internal/pkg/code"
	modelv1 "github.com/pingcap-inc/pchub/model/v1"
	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/log"
)

type LarkEventSrv interface {
	Validate(c *gin.Context, eventStr string) (*EchoData, error)
	DispatcherV1(c context.Context, evt1 modelv1.EventSchemaV1)
	DispatcherV2(c context.Context, evt2 modelv1.EventSchemaV2)
}

type larkEventService struct {
	store store.Factory
}

var _ LarkEventSrv = (*larkEventService)(nil)

type EchoData struct {
	Challenge string `json:"challenge"`
}

func newLarkEvent(srv *service) *larkEventService {
	return &larkEventService{store: srv.store}
}

func (u *larkEventService) Validate(c *gin.Context, eventStr string) (*EchoData, error) {
	var dd EchoData
	if err := json.Unmarshal([]byte(eventStr), &dd); err != nil {
		return nil, err
	}

	return &EchoData{
		Challenge: dd.Challenge,
	}, nil
}

func (u *larkEventService) DispatcherV2(c context.Context, evt2 modelv1.EventSchemaV2) {
	log.L(c).Info("LarkEventSrv.DispatcherV2 called.")

	var er error
	switch evt2.Header.EventType {
	case "im.chat.member.user.added_v1", "im.chat.member.user.withdrawn_v1", "im.chat.member.user.deleted_v1":
		u.handleChatMemberEvent(c, evt2)
	case "im.chat.member.bot.added_v1", "im.chat.member.bot.deleted_v1":
		// simple strategy to handle bot in/out chat event
		// if no deplay, the bot will get chat list before the event took effect
		time.Sleep(5 * time.Second)
		u.handleBotInOutChat(c, evt2)
	}

	if er != nil {
		log.L(c).Errorf("%#+v", er)
	}
}

type ChatMemUID struct {
	UserId string `json:"user_id"`
}

type ChatMemUser struct {
	UserId ChatMemUID `json:"user_id"`
}

type MemEventContent struct {
	ChatId string        `json:"chat_id"`
	Users  []ChatMemUser `json:"users"`
}

func (u *larkEventService) handleBotInOutChat(c context.Context, _ modelv1.EventSchemaV2) {
	log.L(c).Info("LarkEventSrv.handleBotInOutChat called.")

	lc := larkChatService{store: u.store}
	err := lc.SyncChats(c)
	if err != nil {
		log.L(c).Error(err.Error())
	}
}

func (u *larkEventService) handleChatMemberEvent(c context.Context, evt2 modelv1.EventSchemaV2) {
	log.L(c).Info("LarkEventSrv.handleChatMemberEvent called.")

	chatID, _ := evt2.Event["chat_id"].(string)
	users, _ := evt2.Event["users"].([]interface{})
	if chatID != AskPCHChatID || len(users) == 0 {
		return
	}

	e, err := json.Marshal(evt2.Event)
	if err != nil {
		log.Error(err.Error())

		return
	}
	var eContent MemEventContent
	if err := json.Unmarshal(e, &eContent); err != nil {
		log.Error(err.Error())

		return
	}

	isRemove := true
	if evt2.Header.EventType == "im.chat.member.user.added_v1" {
		isRemove = false
	}

	rStore := &rstorage.RedisCluster{
		KeyPrefix: rstore.AskPCHChat,
	}

	for _, u := range eContent.Users {
		if u.UserId.UserId == "" {
			continue
		}
		if isRemove {
			log.Info("Remove user " + u.UserId.UserId + " from ask pch user list in redis")
			rStore.DeleteKey(u.UserId.UserId)
		} else {
			log.Info("Add user " + u.UserId.UserId + " into ask pch user list in redis")
			rStore.SetKey(u.UserId.UserId, "y", 0)
		}
	}
}

// Dispatcher dispatch event.
func (u *larkEventService) DispatcherV1(c context.Context, evt1 modelv1.EventSchemaV1) {
	log.L(c).Info("LarkEventSrv.DispatcherV1 called.")

	eventType, _ := evt1.Event["type"].(string)
	// handle approval status change event
	if eventType == "approval_instance" {
		instanceCode, ok := evt1.Event["instance_code"].(string)
		if !ok {
			log.L(c).Error("cannot get approval instance_code from event")

			return
		}

		instanceSt, ok := evt1.Event["status"].(string)
		if !ok {
			log.L(c).Error("cannot get approval status from event")

			return
		}

		log.L(c).Info("approval(instance_code: " + instanceCode + ") status changed to " + instanceSt)

		approval, err := u.store.Approval().Get(c, &modelv1.Approval{InstanceCode: instanceCode})
		if err != nil {
			log.L(c).Error(errors.Wrap(err, "cannot get corresponding approval record in db").Error())

			return
		}

		approval.Status = instanceSt
		err = u.store.Approval().Update(c, approval)
		if err != nil {
			log.L(c).Error("failed to save approval:" + instanceCode)

			return
		}

		if instanceSt == modelv1.ApprStApproved {
			u.handleApprovalApproved(c, approval)
		}
	}
}

func (u *larkEventService) handleApprovalApproved(c context.Context, approval *modelv1.Approval) {
	log.L(c).Info("LarkEventSrv.handleApprovalApproved called.")

	commonService := &commonService{store: u.store}
	approvalJson, err := commonService.GetDataByReqLarkAPI(c, "GET", LarkOpenAPIBase+"/approval/v4/instances/"+approval.InstanceCode, nil, nil)
	if err != nil {
		log.L(c).Error(errors.Wrap(err, "cannot get approval from lark, instanceID: "+approval.InstanceCode).Error())

		return
	}

	approvalInLark := &modelv1.ApprovalInLark{}
	if err = json.Unmarshal(approvalJson, approvalInLark); err != nil {
		log.L(c).Error(errors.Wrap(err, "Error when unmarshal lark approval instance data, instanceID: "+approval.InstanceCode).Error())

		return
	}

	var formList []map[string]interface{}
	if err = json.Unmarshal([]byte(approvalInLark.Form), &formList); err != nil {
		log.L(c).Error(errors.Wrap(err, "Error when unmarshal lark approval form, instanceID: "+approval.InstanceCode).Error())

		return
	}

	switch approval.ApprovalType {
	case modelv1.TypeApplyAccount:
		u.handleApplyAccount(c, approval, &formList, approvalJson)
	case modelv1.TypeJoinAccount:
		u.handleJoinAccount(c, approval, &formList, approvalJson)
	case modelv1.TypeChangeBudget:
		u.handleChangeBudget(c, approval, &formList, approvalJson)
	case modelv1.TypeInviteUser:
		u.handleInviteUser(c, approval, &formList, approvalJson)
	case modelv1.TypeCloseAccount:
		u.handleCloseAccount(c, approval, &formList, approvalJson)
	case modelv1.TypeRemoveUser:
		u.handleRemoveUser(c, approval, &formList, approvalJson)
	default:
		log.L(c).Error("unknown approval type: " + strconv.Itoa(approval.ApprovalType))
	}
}

func (u *larkEventService) handleCloseAccount(c context.Context, approval *modelv1.Approval, _ *[]map[string]interface{}, approvalJson json.RawMessage) {
	log.L(c).Info("LarkEventSrv.handleCloseAccount called, approval instance id: " + approval.InstanceCode)

	// soft delete resource
	var cForm struct {
		ApprovalType int               `json:"approvalType"`
		Form         modelv1.CloseForm `json:"form"`
	}
	err := json.Unmarshal([]byte(approval.RequestBodyShadow), &cForm)
	if err != nil {
		log.L(c).Error(err.Error())

		return
	}

	err = u.store.CloudResource().Delete(c, &modelv1.CloudResource{
		ManagementID: cForm.Form.ManagementId,
	})
	if err != nil {
		log.L(c).Error("cannot delete cloud resource(" + cForm.Form.ManagementId + "), err: " + err.Error())

		return
	}

	log.L(c).Infof("success to sync approved 'ChangeBudget' approval into db, approval instance id: ", approval.InstanceCode)

	// save approval instance shadow
	approval.LarkInsShadow = string(approvalJson)
	err = u.store.Approval().Update(c, approval)
	if err != nil {
		log.L(c).Error("failed to save approval shadow, approval instance id:" + approval.InstanceCode)

		return
	}
}

//nolint:gocyclo,cyclop // this function is too complex, but it's ok
func (u *larkEventService) handleApplyAccount(c context.Context, approval *modelv1.Approval, formList *[]map[string]interface{}, approvalJson json.RawMessage) {
	log.L(c).Info("LarkEventSrv.handleApplyAccount called, approval instance id: " + approval.InstanceCode)

	// create resource
	var aForm struct {
		ApprovalType int               `json:"approvalType"`
		Form         modelv1.ApplyForm `json:"form"`
	}
	err := json.Unmarshal([]byte(approval.RequestBodyShadow), &aForm)
	if err != nil {
		log.L(c).Error(err.Error())

		return
	}

	pro, err := u.store.CloudProvider().GetByCode(c, aForm.Form.Provider)
	if err != nil {
		log.L(c).Error("cannot get provider by code, " + err.Error())

		return
	}

	widgets, err := u.store.LarkApprWidget().ListByApprCode(c, modelv1.ApprovalCodeMap[approval.ApprovalType])
	if err != nil {
		log.L(c).Error("cannot get lark appr widget list in db, approvalDefCode: " + modelv1.ApprovalCodeMap[approval.ApprovalType])

		return
	}
	fwMap := make(map[string]modelv1.LarkApprovalWidget)
	for _, widget := range *widgets {
		fwMap[widget.FieldCode] = widget
	}
	fdMap := make(map[string]map[string]interface{})
	for _, f := range *formList {
		id, ok := f["id"].(string)
		if ok {
			fdMap[id] = f
		}
	}
	mngIDI, err := ExtractLarkApprWidget(c, &fwMap, &fdMap, "managementID")
	mngID, ok := mngIDI.(string)
	if err != nil || !ok {
		log.L(c).Error("cannot extract managementID from 'form' field of lark approval instance")

		return
	}
	cloudInsIDI, err := ExtractLarkApprWidget(c, &fwMap, &fdMap, "cloudInsID")
	cloudInsID, ok := cloudInsIDI.(string)
	if err != nil || !ok {
		log.L(c).Error("cannot extract cloudInsID from 'form' field of lark approval instance")

		return
	}

	classTypeI, err := ExtractLarkApprWidget(c, &fwMap, &fdMap, "classType")
	var classType string
	if err == nil {
		classType, ok = classTypeI.(string)
		if !ok {
			log.L(c).Error("cannot extract classType from 'form' field of lark approval instance")

			return
		}
	}

	apprUserLarkID := approval.Luid
	r, err := u.store.CloudResource().Create(c, &modelv1.CloudResource{
		ManagementID:   mngID,
		CloudInsID:     cloudInsID,
		ProviderID:     pro.ID,
		Name:           aForm.Form.Name,
		OwnerLarkID:    apprUserLarkID,
		Desc:           aForm.Form.Description,
		DepartLarkID:   aForm.Form.Department,
		ClassType:      classType,
		ManagementType: 1,
	})
	if err != nil {
		log.L(c).Error("failed to create resource, err: " + err.Error())

		return
	}

	for _, bit := range aForm.Form.BudgetItems {
		n := &modelv1.CloudResourceBudget{
			ResourceID: r.ID,
			Amount:     bit.Budget,
			Month:      bit.Month,
		}

		_, err = u.store.CloudBudget().Create(c, n)
		if err != nil {
			log.L(c).Error("failed to create budget, err: " + err.Error())

			return
		}
	}

	log.L(c).Infof("success to sync approved 'ApplyAccount' approval into db, approval instance id: ", approval.InstanceCode)

	// save approval instance shadow
	approval.LarkInsShadow = string(approvalJson)
	err = u.store.Approval().Update(c, approval)
	if err != nil {
		log.L(c).Error("failed to save approval shadow, approval instance id:" + approval.InstanceCode)

		return
	}
}

func (u *larkEventService) handleChangeBudget(c context.Context, approval *modelv1.Approval, _ *[]map[string]interface{}, approvalJson json.RawMessage) {
	log.L(c).Info("LarkEventSrv.handleChangeBudget called, approval instance id: " + approval.InstanceCode)

	// create budget for corresponding resource
	var cbForm struct {
		ApprovalType int                      `json:"approvalType"`
		Form         modelv1.BudgetChangeForm `json:"form"`
	}
	err := json.Unmarshal([]byte(approval.RequestBodyShadow), &cbForm)
	if err != nil {
		log.L(c).Error(err.Error())

		return
	}

	r, err := u.store.CloudResource().Get(c, &modelv1.CloudResource{
		ManagementID: cbForm.Form.ManagementId,
	})
	if err != nil {
		log.L(c).Error("corresponding cloud resource(" + cbForm.Form.ManagementId + ") not found, err: " + err.Error())

		return
	}

	for _, bit := range cbForm.Form.BudgetItems {
		if cbForm.Form.BudgetType == modelv1.BudgetApprTypeNew {
			n := &modelv1.CloudResourceBudget{
				ResourceID: r.ID,
				Amount:     bit.After,
				Month:      bit.Month,
			}

			_, err = u.store.CloudBudget().Create(c, n)
			if err != nil {
				log.L(c).Error("failed to create budget, err: " + err.Error())

				return
			}
		} else {
			err = u.store.CloudBudget().UpdateBudget(c, r.ID, bit.Month, bit.After)
			if err != nil {
				log.L(c).Error("failed to update budget, err: " + err.Error())

				return
			}
		}
	}

	log.L(c).Infof("success to sync approved 'ChangeBudget' approval into db, approval instance id: ", approval.InstanceCode)

	// save approval instance shadow
	approval.LarkInsShadow = string(approvalJson)
	err = u.store.Approval().Update(c, approval)
	if err != nil {
		log.L(c).Error("failed to save approval shadow, approval instance id:" + approval.InstanceCode)

		return
	}
}

//nolint:gocyclo,cyclop,funlen,gocognit // complex
func (u *larkEventService) handleJoinAccount(c context.Context, approval *modelv1.Approval, formList *[]map[string]interface{}, approvalJson json.RawMessage) {
	log.L(c).Info("LarkEventSrv.handleJoinAccount called, approval instance id: " + approval.InstanceCode)

	// create member access record for corresponding resource
	widgets, err := u.store.LarkApprWidget().ListByApprCode(c, modelv1.ApprovalCodeMap[approval.ApprovalType])
	if err != nil {
		log.L(c).Error("cannot get lark appr widget list in db, approvalDefCode: " + modelv1.ApprovalCodeMap[approval.ApprovalType])

		return
	}

	fwMap := make(map[string]modelv1.LarkApprovalWidget)
	for _, widget := range *widgets {
		fwMap[widget.FieldCode] = widget
	}
	fdMap := make(map[string]map[string]interface{})
	for _, f := range *formList {
		id, ok := f["id"].(string)
		if ok {
			fdMap[id] = f
		}
	}

	apprUserLarkID := approval.Luid
	mngIDI, err := ExtractLarkApprWidget(c, &fwMap, &fdMap, "managementID")
	mngID, ok := mngIDI.(string)
	if err != nil || !ok {
		log.L(c).Error("cannot extract managementID from 'form' field of lark approval intance")

		return
	}
	r, err := u.store.CloudResource().Get(c, &modelv1.CloudResource{
		ManagementID: mngID,
	})
	if err != nil {
		log.L(c).Error("corresponding cloud resource(" + mngID + ") not found, err: " + err.Error())

		return
	}

	noteI, err := ExtractLarkApprWidget(c, &fwMap, &fdMap, "note")
	note, ok := noteI.(string)
	if err != nil || !ok {
		log.L(c).Error("cannot extract note from 'form' field of lark approval intance")

		return
	}

	rolesI, err := ExtractLarkApprWidget(c, &fwMap, &fdMap, "roles")
	roles, ok := rolesI.([]interface{})
	if err != nil || !ok {
		log.L(c).Error("cannot extract roles from 'form' field of lark approval intance")

		return
	}
	rolesInDB, err := u.store.CloudMemberRole().ListByProviderName(c, r.Provider.Name)
	if err != nil {
		log.L(c).Error("cannot get roles in db for provider name: " + r.Provider.Name)

		return
	}
	rMap := make(map[string]*modelv1.CloudMemberRole)
	for _, rit := range rolesInDB {
		rMap[rit.Name] = rit
	}

	for _, rName := range roles {
		rNameStr, _ := rName.(string)

		if rNameStr == "" {
			log.L(c).Warnf("role name is empty:", approval.InstanceCode)

			continue
		}
		nItem := &modelv1.ResourceMemberAccess{
			MemberLarkID: apprUserLarkID,
			ResourceID:   r.ID,
			RoleID:       rMap[rNameStr].ID,
			RequestNote:  note,
			TimeTag:      time.Unix(0, 0).Unix(),
		}

		// Check if the record already exists to prevent duplicate key error
		_, err := u.store.ResourceMemberAccess().Get(c, nItem)
		if err == nil {
			log.L(c).Info("member access record already exists, skip")

			continue
		}

		_, err = u.store.ResourceMemberAccess().Create(c, nItem)
		if err != nil {
			log.L(c).Error("cannot create member access record in db, err: " + err.Error())

			continue
		}
	}

	log.L(c).Infof("success to sync approved 'JoinAccount' approval into db, approval instance id: ", approval.InstanceCode)

	// save approval instance shadow
	approval.LarkInsShadow = string(approvalJson)
	err = u.store.Approval().Update(c, approval)
	if err != nil {
		log.L(c).Error("failed to save approval shadow, approval instance id:" + approval.InstanceCode)

		return
	}

	// sync the option to cloud
	rolesInCloud := make([]*modelv1.RoleMembers, 0)
	user, err := u.store.User().GetByAnyID(c, apprUserLarkID)
	if err != nil {
		log.L(c).Error("cannot get user in db, lark id: " + apprUserLarkID)

		return
	}
	for _, role := range roles {
		rStr, _ := role.(string)
		if rStr == "" {
			log.L(c).Warnf("role name is empty:", approval.InstanceCode)

			continue
		}
		roleMembers := &modelv1.RoleMembers{
			Role:    rMap[rStr],
			Members: []*modelv1.User{user},
		}
		rolesInCloud = append(rolesInCloud, roleMembers)
	}
	rmService := resourceManagerService{
		store: u.store,
	}
	err = rmService.SyncToCloud(c, rolesInCloud, r, modelv1.Add)
	if err != nil {
		log.L(c).Error("failed to sync to cloud, err: " + err.Error())

		return
	}
}

func (u *larkEventService) handleInviteUser(c context.Context, approval *modelv1.Approval, _ *[]map[string]interface{}, approvalJson json.RawMessage) {
	log.L(c).Info("LarkEventSrv.handleInviteUser called, approval instance id: " + approval.InstanceCode)

	// create member access
	var iForm struct {
		ApprovalType int                `json:"approvalType"`
		Form         modelv1.InviteForm `json:"form"`
	}
	err := json.Unmarshal([]byte(approval.RequestBodyShadow), &iForm)
	if err != nil {
		log.L(c).Error(err.Error())

		return
	}

	r, err := u.store.CloudResource().Get(c, &modelv1.CloudResource{
		ManagementID: iForm.Form.ManagementId,
	})
	if err != nil {
		log.L(c).Error("corresponding cloud resource(" + iForm.Form.ManagementId + ") not found, err: " + err.Error())

		return
	}

	rolesInDB, err := u.store.CloudMemberRole().ListByProviderCode(c, r.Provider.Code)
	if err != nil {
		log.L(c).Error("cannot get roles in db for provider code: " + r.Provider.Code)

		return
	}
	rMap := make(map[string]*modelv1.CloudMemberRole)
	for _, rit := range rolesInDB {
		rMap[rit.Code] = rit
	}

	for _, it := range iForm.Form.InviteUsers {
		nItem := &modelv1.ResourceMemberAccess{
			MemberLarkID: it.Member,
			ResourceID:   r.ID,
			RoleID:       rMap[it.RoleCode].ID,
			TimeTag:      time.Unix(0, 0).Unix(),
		}

		_, err := u.store.ResourceMemberAccess().Get(c, nItem)
		if err == nil {
			log.L(c).Info("member access record already exists, skip")

			continue
		}

		nItem.RequestNote = iForm.Form.Description

		_, err = u.store.ResourceMemberAccess().Create(c, nItem)
		if err != nil {
			log.L(c).Error("cannot create member access record in db, err: " + err.Error())

			return
		}
	}

	log.L(c).Infof("success to sync approved 'JoinAccount' approval into db, approval instance id: ", approval.InstanceCode)

	// save approval instance shadow
	approval.LarkInsShadow = string(approvalJson)
	err = u.store.Approval().Update(c, approval)
	if err != nil {
		log.L(c).Error("failed to save approval shadow, approval instance id:" + approval.InstanceCode)

		return
	}

	// sync the option to cloud
	rolesInCloud := make([]*modelv1.RoleMembers, 0)
	rmMap := make(map[string][]string)
	uMap := make(map[string]*modelv1.User)
	luids := make([]string, 0)
	for _, it := range iForm.Form.InviteUsers {
		luids = append(luids, it.Member)
		rmMap[it.RoleCode] = append(rmMap[it.RoleCode], it.Member)
	}
	usersInDb, err := u.store.User().MGetByLarkIDs(c, luids)
	if err != nil {
		log.L(c).Error("cannot get user in db, lark ids: " + strings.Join(luids, ","))

		return
	}
	for _, u := range usersInDb {
		uMap[u.LarkUserID] = u
	}
	for roleCode, larkIds := range rmMap {
		var users []*modelv1.User
		for _, luid := range larkIds {
			users = append(users, uMap[luid])
		}
		roleMembers := &modelv1.RoleMembers{
			Role:    rMap[roleCode],
			Members: users,
		}
		rolesInCloud = append(rolesInCloud, roleMembers)
	}

	rmService := resourceManagerService{
		store: u.store,
	}
	err = rmService.SyncToCloud(c, rolesInCloud, r, modelv1.Add)
	if err != nil {
		log.L(c).Error("failed to sync to cloud, err: " + err.Error())

		return
	}
}

func (u *larkEventService) handleRemoveUser(c context.Context, approval *modelv1.Approval, _ *[]map[string]interface{}, approvalJson json.RawMessage) {
	log.L(c).Info("LarkEventSrv.handleRemoveUser called, approval instance id: " + approval.InstanceCode)

	// create member access
	var iForm struct {
		ApprovalType int                `json:"approvalType"`
		Form         modelv1.RemoveForm `json:"form"`
	}
	err := json.Unmarshal([]byte(approval.RequestBodyShadow), &iForm)
	if err != nil {
		log.L(c).Error(err.Error())

		return
	}

	r, err := u.store.CloudResource().Get(c, &modelv1.CloudResource{
		ManagementID: iForm.Form.ManagementId,
	})
	if err != nil {
		log.L(c).Error("corresponding cloud resource(" + iForm.Form.ManagementId + ") not found, err: " + err.Error())

		return
	}

	for _, user := range iForm.Form.Members {
		mrs, err := u.store.ResourceMemberAccess().ListByLarkID(c, user)
		if err != nil {
			log.L(c).Error("cannot get member access record in db, err: " + err.Error())

			return
		}

		var ids []uint64
		for _, mr := range mrs {
			ids = append(ids, mr.ID)
		}
		err = u.store.ResourceMemberAccess().DeleteByIDs(c, ids)
		if err != nil {
			log.L(c).Error("cannot delete member access record in db, err: " + err.Error())

			return
		}
	}

	log.L(c).Infof("success to sync approved 'RemoveUser' approval into db, approval instance id: ", approval.InstanceCode)

	// save approval instance shadow
	approval.LarkInsShadow = string(approvalJson)
	err = u.store.Approval().Update(c, approval)
	if err != nil {
		log.L(c).Error("failed to save approval shadow, approval instance id:" + approval.InstanceCode)

		return
	}

	// sync the option to cloud
	var rolesInCloud []*modelv1.RoleMembers
	var luids []string
	luids = append(luids, iForm.Form.Members...)
	usersInDb, err := u.store.User().MGetByLarkIDs(c, luids)
	if err != nil {
		log.L(c).Error("cannot get user in db, lark ids: " + strings.Join(luids, ","))

		return
	}
	roleMembers := &modelv1.RoleMembers{
		Members: usersInDb,
	}
	rolesInCloud = append(rolesInCloud, roleMembers)

	rmService := resourceManagerService{
		store: u.store,
	}
	err = rmService.SyncToCloud(c, rolesInCloud, r, modelv1.DeleteAll)
	if err != nil {
		log.L(c).Error("failed to sync to cloud, err: " + err.Error())

		return
	}
}

func ExtractLarkApprWidget(ctx context.Context, fwMap *map[string]modelv1.LarkApprovalWidget, fdMap *map[string]map[string]interface{}, k string) (interface{}, error) {
	widgetID := (*fwMap)[k].LarkWidgetID
	if widgetID == "" {
		err := errors.WithCode(code.ErrValidation, k+"-larkWidgetID not found")
		log.L(ctx).Error(err.Error())

		return nil, err
	}

	widgetItem, ok := (*fdMap)[widgetID]
	if !ok {
		err := errors.WithCode(code.ErrValidation, "corresponding widget valueItem not found")
		log.L(ctx).Error(err.Error())

		return nil, err
	}

	widgetType, ok := widgetItem["type"].(string)
	if !ok || widgetType == "" {
		err := errors.WithCode(code.ErrValidation, "cannot find widget type")
		log.L(ctx).Error(err.Error())

		return nil, err
	}

	return widgetItem["value"], nil
}
