package receive

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tidbcloud/account/commons/eventbus"

	dbuserSvc "github.com/tidbcloud/account/internel/svc/dbuser"
	"github.com/tidbcloud/account/internel/svc/rbac"
	usersvc "github.com/tidbcloud/account/internel/svc/user"

	"github.com/tidbcloud/account/commons/redis"
	"go.opentelemetry.io/otel"

	"github.com/tidbcloud/account/central/utils"

	"github.com/tidbcloud/account/thirdparty/infra"

	userDao "github.com/tidbcloud/account/internel/dao/user"

	"github.com/tidbcloud/account/commons/config"

	"github.com/tidbcloud/account/internel/dao/dbuser"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

func RegisterClusterEventHandler(dbUserSrv dbuserSvc.DBUserIface) error {
	eventHandlerFunc := func(e *eventbus.Event) error {
		return receiveClusterEvent(e, dbUserSrv)
	}

	err := eventbus.Get().RegisterEventHandleFunc(config.EventType, config.Group, ClusterEvent{}, eventHandlerFunc)
	if err != nil {
		tilog.Error(context.Background(), "RegisterEventHandleFunc", zap.Error(err))
		return err
	}

	return nil
}

// receiveClusterEvent
//
//		@Description:  receive cluster create event from cm 。 create: create org_key  if  record not exist; delete:delete cluster cache

// @Author wangletian 2022-11-24 16:36:01
// @param event
// @param tiDBUserSrv
// @return error
func receiveClusterEvent(event *eventbus.Event, dbUserSrv dbuserSvc.DBUserIface) error {
	ctx, span := otel.Tracer(config.TraceComponentNameCentral).Start(context.Background(), "ClusterEvent-CreateOrgKey")
	defer span.End()
	userSvc, err := usersvc.GetUserSvc()
	if err != nil {
		tilog.Error(ctx, "LoadUserSvc error", zap.Any("err", err))
		return err
	}
	data := event.EventData.(*ClusterEvent)

	if data.ClusterType == ClusterTypeTidbxResourcePool {
		tilog.Info(ctx, "receiveClusterEvent ClusterTypeTidbxResourcePool, ignore", zap.Any("eventData", data))
		return nil
	}
	// cluster create
	if ClusterEventTypeCreating == data.EventType && data.OperatorID != 0 {
		tilog.Info(ctx, "receiveClusterEvent ClusterEventTypeCreating", zap.Any("eventData", data))

		clusterFilter := userDao.LandingActionFilter{
			UserIDs:   []entity.ID{entity.ID(data.OperatorID)},
			TargetIDs: []entity.ID{entity.ID(data.ClusterID)},
		}
		tilog.Info(ctx, "clusterId isExist  ", zap.Any("clusterId", data.ClusterID), zap.Any("clusterFilter", clusterFilter))
		isExist, err := userSvc.IsExist(ctx, clusterFilter)
		if err != nil {
			tilog.Error(ctx, "IsExist fail ", zap.Any("clusterFilter", clusterFilter), zap.Error(err))
		}
		tilog.Info(ctx, "clusterId isExist result ", zap.Any("isExist", isExist), zap.Any("clusterId", data.ClusterID))

		if !*isExist {
			userLandingAction := &entity.UserLandingAction{
				UserID:       entity.ID(data.OperatorID),
				TargetAction: entity.CreateClusterAction,
				TargetID:     entity.ID(data.ClusterID),
			}
			queryEntity := entity.UserLandingAction{
				UserID:       entity.ID(data.OperatorID),
				TargetAction: entity.CreateClusterAction,
			}
			tilog.Info(ctx, "CreateUserLandingAction", zap.Any("userLandingAction", userLandingAction))
			if err := userSvc.SaveFirstAction(ctx, queryEntity, userLandingAction); err != nil {
				tilog.Error(ctx, "CreateUserLandingAction fail ", zap.Any("userLandingAction", userLandingAction), zap.Error(err))
			}
		}
	} else if ClusterEventTypeLaunching == data.EventType {
		tilog.Info(ctx, "receiveClusterEvent ClusterEventTypeLaunching", zap.Any("eventData", data))
		// DedicatedCluster cluster
		if !data.IsDevTier {
			filter := dbuser.OrgKeyFilter{
				OrgIDs: []entity.ID{entity.ID(data.TenantID)},
				Status: []entity.OrgKeyStatus{entity.OrgKeyStatusActive},
			}

			orgKes, err := dbUserSrv.ListOrgKeys(ctx, filter)
			if err != nil {
				return err
			}
			tilog.Info(ctx, "query  ListOrgKeys result",
				zap.Any("filter", filter),
				zap.Any("orgKes", orgKes))
			if len(orgKes) == 0 {
				tilog.Info(ctx, "OrgKey is not exist,need create", zap.Any("orgId", data.TenantID), zap.Any("clusterId", data.ClusterID))
				// will send create OrgKey command
				if err := dbUserSrv.CreateCommandOrgKeyIfNotExist(ctx, data.TenantID, entity.CommandStatusWaiting, entity.CommandActionCreate); err != nil {
					return err
				}
			}
			err = dbUserSrv.CreateCommandClusterKey(ctx, data.TenantID, data.ClusterID, entity.CommandStatusWaiting, entity.CommandActionCreate)
			if err != nil {
				tilog.Error(ctx, "CreateCommandClusterKey error", zap.Any("clusterId", data.ClusterID), zap.Any("OrgId", data.TenantID))
				return err
			}
			tilog.Info(ctx, "CreateCommandClusterKey success", zap.Any("clusterId", data.ClusterID), zap.Any("OrgId", data.TenantID))
		}
	} else if ClusterEventTypeDeleted == data.EventType || ClusterEventTypeDeleting == data.EventType {
		tilog.Info(ctx, "receiveClusterEvent ClusterEventTypeDeleted", zap.Any("eventData", data))
		if data.ClusterID != 0 {
			c := infra.GetClusterCache(ctx)
			key := strconv.FormatUint(data.ClusterID, 10)
			c.Delete(key)
			tilog.Info(ctx, fmt.Sprintf("delete cluster cache ,cluster_id=%v", key))
			redisClient := redis.GetRedisClient()
			tilog.Info(ctx, fmt.Sprintf("publish cluster ,cluster_id=%v", key))
			redisClient.Publish(ctx, redisChanel, data.ClusterID)
		}
	} else if ClusterEventTypeCreated == data.EventType {
		tilog.Info(ctx, "receiveClusterEvent ClusterEventTypeCreated", zap.Any("eventData", data))
		svc, _ := rbac.GetRbacUserSvc()
		orgUsers, err := svc.ListUsersByScope(ctx, entity.RoleBindingScopeOrg, data.TenantID)
		if err != nil {
			tilog.Error(ctx, "receiveClusterEvent ListUsersByScope err", zap.Any("err", err))
			return err
		}
		var operatorID uint64 = 0
		for _, orgUser := range orgUsers {
			if orgUser.RoleBindingStatus == entity.RoleBindingStatusActive && orgUser.RbacRole == entity.RoleOrgOwner {
				operatorID = orgUser.User.ID.Uint64()
				break
			}
		}

		cluster, err := infra.GetHandler().GetClusterById(ctx, data.ClusterID, true)
		if err != nil {
			tilog.Error(ctx, "receiveClusterEvent GetClusterById err", zap.Any("err", err))
			return err
		}
		if cluster == nil {
			tilog.Info(ctx, "receiveClusterEvent GetClusterById return nil, ignore", zap.Any("err", err))
			return nil
		}
		flag := !cluster.IsDevTier

		projectUsers, err := svc.ListUsersByScope(ctx, entity.RoleBindingScopeProject, data.ProjectID)
		if err != nil {
			tilog.Error(ctx, "receiveClusterEvent ListUsersByScope err", zap.Any("err", err))
			return err
		}
		var objs []entity.CommandDBUserCluster
		for _, projectUser := range projectUsers {
			if projectUser.RoleBindingStatus != entity.RoleBindingStatusActive {
				continue
			}
			// ignore admin db user
			if projectUser.RbacRole == entity.RoleProjectOwner {
				continue
			}
			var createDBUser *string
			req := utils.GetDBUserReq{Email: projectUser.User.Email,
				ClusterId:       &cluster.Id,
				IsDedicated:     &flag,
				ServerlessToken: cluster.ServerlessToken}

			createDBUser, err := utils.GetDBUser(ctx, req)
			if err != nil {
				tilog.Error(ctx, "receiveClusterEvent GetDBUser err", zap.Any("projectUser", projectUser), zap.Any("err", err))
				continue
			}
			createDBRole, ok := entity.RoleBindingRole2DbRoleMap[projectUser.RbacRole]
			if !ok {
				tilog.Info(ctx, "receiveClusterEvent db role not match", zap.Any("projectUser", projectUser), zap.Any("err", err))
				continue
			}
			if cluster.IsDevTier && createDBRole != entity.DbRoleAdmin {
				createDBRole = *cluster.ServerlessToken + "." + createDBRole
			}

			obj := entity.CommandDBUserCluster{
				OrgID:        entity.ID(cluster.TenantId),
				Operator:     entity.ID(operatorID),
				UserID:       entity.ID(projectUser.User.ID.Uint64()),
				ClusterID:    entity.ID(cluster.Id),
				DbUser:       *createDBUser,
				Status:       entity.CommandStatusWaiting,
				Action:       entity.CommandActionCreate,
				TargetType:   entity.ClusterUserTargetUser,
				DbRole:       createDBRole,
				OperatorType: entity.ClusterUserTargetUser,
			}
			objs = append(objs, obj)
		}
		if len(objs) > 0 {
			err := dbUserSrv.BatchCreateCommandDBUserCluster(ctx, objs)
			if err != nil {
				tilog.Error(ctx, "receiveClusterEvent BatchCreateCommandDBUserCluster err", zap.Any("objs", objs), zap.Any("err", err))
				return err
			}
			tilog.Info(ctx, "receiveClusterEvent BatchCreateCommandDBUserCluster success")
		} else {
			tilog.Info(ctx, "receiveClusterEvent not found project user, no need to CreateCommandDBUserCluster")
		}
	}

	return nil
}
