package events

import (
	"context"
	"time"

	"github.com/tidbcloud/account/internel/svc/token"

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

	orgsvc "github.com/tidbcloud/account/internel/svc/org"
	"github.com/tidbcloud/account/internel/svc/org/handler"
	projectsvc "github.com/tidbcloud/account/internel/svc/project"
	"github.com/tidbcloud/account/internel/svc/rbac"
	usersvc "github.com/tidbcloud/account/internel/svc/user"

	"github.com/duke-git/lancet/v2/slice"

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

	"go.opentelemetry.io/otel"

	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"

	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/internel/dao/role"
	"github.com/tidbcloud/account/thirdparty/infra"
	"gorm.io/gorm"

	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

const (
	tokenExpire = 24
	//regionDev   = "us-east-1"
	//RegionProd  = "eu-central-1"
	clusterName = "Cluster0"
	provider    = "aws"
)

var (
	skipSignupSource   = []string{"OAuth", "oauth", "pingcap-en-dedicated", "pingcap-jp-dedicated"}
	skipProviderSource = []string{"alicloud"}
)

// RegisterUserRegisterHandler
//
//	@Description:  RegistrationEvent when server run
//	@Author wangletian 2023-01-12 20:53:29
//	@return error
func RegisterUserRegisterHandler() error {
	eventHandlerFunc := func(event *eventbus.Event) error {
		return receiveRegisterEvent(event)
	}

	err := eventbus.Get().RegisterEventHandleFunc(config.EventTypeRegister, config.Group, RegisterEvent{}, eventHandlerFunc)
	if err != nil {
		tilog.Error(context.Background(), "RegisterEventHandleFunc fail", zap.Error(err))
		return err
	}
	tilog.Info(context.Background(), "RegisterEventHandleFunc success", zap.Error(err))
	return nil
}

// ReceiveRegistrationEvent
//
//	@Description:  receive the event
//	@Author wangletian 2023-01-11 16:10:29
//	@param event
func receiveRegisterEvent(event *eventbus.Event) error {
	ctx, span := otel.Tracer(config.TraceComponentNameCentral).Start(context.Background(), "RegisterEvent-CreateServerless")
	defer span.End()
	//ctx := context.Background()
	data := event.EventData.(*RegisterEvent)
	sub := time.Since(data.EventTime).Seconds()
	tilog.Info(ctx, "receive register event", zap.Any("userId", data.UserId), zap.Any("email", data.Email),
		zap.Any("eventTime", data.EventTime), zap.Any("channel", data.Channel), zap.Any("sub time", sub))
	if sub >= 5 {
		tilog.Error(ctx, "receive register event delay", zap.Any("delay second", sub), zap.Any("data.EventTime", data.EventTime), zap.Any("data.UserId", data.UserId), zap.Any("data.Email", data.Email))
	}

	CreateServerless(ctx, data.UserId, data.EventTime, data.ClientIP)
	return nil
}

// CreateServerless
//
//	@Description: CreateServerless for new user
//	@Author wangletian 2023-01-12 20:51:49
//	@param ctx
//	@param userId
//	@param eventTime
//	@param clientIP
func CreateServerless(ctx context.Context, userId uint64, eventTime time.Time, clientIP string) {

	userSvc, _ := usersvc.GetUserSvc()
	registrationLog, err := userSvc.GetUserRegistrationLog(ctx, userDao.RegistrationLogParam{UserId: userId})
	if err != nil {
		tilog.Error(ctx, "GetUserRegistrationLog error", zap.Any("userID", userId), zap.Any("err", err))
		return
	}
	if registrationLog != nil {
		if slice.Contain(skipSignupSource, registrationLog.SignupSource) {
			tilog.Info(ctx, "skip CreateServerless, the user signup_source  ", zap.Any("userId", userId), zap.Any("registrationLog.SignupSource", registrationLog.SignupSource))
			return
		}
		if slice.Contain(skipProviderSource, registrationLog.ProviderSource) {
			tilog.Info(ctx, "skip CreateServerless, the user provider_source  ", zap.Any("userId", userId), zap.Any("registrationLog.ProviderSource", registrationLog.ProviderSource))
			return
		}
	}

	u, _ := userSvc.GetUserByID(ctx, userId)
	tilog.Info(ctx, "CreateServerless start", zap.Any("userId", u.ID), zap.Any("email", u.Email))
	tilog.Info(ctx, "CreateServerless  ", zap.Any("user", u.ID))
	projectId, err := getUserActiveProject(ctx, u.ID.Uint64())
	if err == nil && projectId != nil {
		projectSvc, _ := projectsvc.GetProjectSvc()

		orgSvc, _ := orgsvc.GetOrgSvc()
		//ListOrgsByUserID
		orgs, err := orgSvc.ListOrgsByUserID(ctx, u.ID.Uint64())
		tilog.Info(ctx, "query user orgs ", zap.Any("orgs", orgs))
		if err != nil {
			tilog.Error(ctx, "GetUserOrg", zap.Error(err))
		}

		if len(orgs) == 1 {
			org := orgs[0]
			if org.Status != entity.TenantStatusActive {
				return
			}
			project, err := projectSvc.GetProjectByID(ctx, projectId.Uint64())
			if err != nil {
				if err == gorm.ErrRecordNotFound {
					tilog.Info(ctx, "GetProjectByID  the project id is not exits", zap.Any("projectId", projectId.Uint64()))
				}
				tilog.Error(ctx, "GetProjectByID fail", zap.Any("projectId", projectId.Uint64()))
			}
			tilog.Info(ctx, "GetProjectByID ", zap.Any("project", project), zap.Any("org", org))
			tilog.Info(ctx, "project.CreatedAt and  org.CreatedAt", zap.Any("project.CreatedAt", project.CreatedAt), zap.Any("org.CreatedAt", orgs[0].CreatedAt), zap.Any("time now", time.Now()))
			if org.Email == u.Email && org.CreatedAt.Sub(project.CreatedAt).Seconds() < 20 && time.Since(org.CreatedAt).Hours() < tokenExpire {
				tilog.Info(ctx, "will create Serverless Cluster for this new user", zap.Any("userId", u.ID))
				clusterId, err := callCMCreateCluster(ctx, u.ID.Uint64(), org.ID.Uint64(), projectId.Uint64(), clientIP)
				if err != nil {
					tilog.Info(ctx, "call CM CreateCluster fail ", zap.Error(err))
					return
				}
				sub := time.Since(eventTime).Seconds()
				tilog.Info(ctx, "CreateCluster success", zap.Any("data.UserId", u.ID), zap.Any("data.Email", u.Email), zap.Any("clusterId", clusterId), zap.Any("create cluster sub time", sub))
				if sub >= 2 {
					tilog.Info(ctx, "create Serverless Cluster for  new user delay", zap.Any("delay second", sub), zap.Any("data.UserId", u.ID), zap.Any("data.Email", u.Email), zap.Any("create cluster sub time", sub), zap.Any("clusterId", clusterId))
				}
				userLandingAction := &entity.UserLandingAction{
					UserID:       u.ID,
					TargetAction: entity.DefaultCreateClusterAction,
					TargetID:     entity.ID(*clusterId)}

				if err := userSvc.CreateUserLandingAction(ctx, userLandingAction); err != nil {
					tilog.Error(ctx, "SaveFirstAction fail ", zap.Any("userLandingAction", userLandingAction), zap.Error(err))
				}
				defClusterProjectAction := &entity.UserLandingAction{
					UserID:       u.ID,
					TargetAction: entity.DefaultClusterInProjectAction,
					TargetID:     *projectId,
				}
				if err := userSvc.CreateUserLandingAction(ctx, defClusterProjectAction); err != nil {
					tilog.Error(ctx, "Create User DefaultClusterInProjectAction fail ", zap.Any("userLandingAction", defClusterProjectAction), zap.Error(err))
				}
				defClusterOrgAction := &entity.UserLandingAction{
					UserID:       u.ID,
					TargetAction: entity.DefaultClusterInOrgAction,
					TargetID:     org.ID,
				}
				if err := userSvc.CreateUserLandingAction(ctx, defClusterOrgAction); err != nil {
					tilog.Error(ctx, "Create User DefaultClusterInOrgAction fail ", zap.Any("userLandingAction", defClusterOrgAction), zap.Error(err))
				}
				return
			}
			tilog.Info(ctx, "project.CreatedAt and  org.CreatedAt do not comply with the rules,skip ", zap.Any("user", u), zap.Any("project", project), zap.Any("org", org))

		} else {
			tilog.Info(ctx, "this user is not a new user ", zap.Any("user", u))
		}
	}
}

func getUserActiveProject(ctx context.Context, userId uint64) (*entity.ID, error) {
	svc, _ := rbac.GetRbacRoleBindingSvc()
	filter := role.RoleBindingFilter{
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		TargetIDs:  []entity.ID{entity.ID(userId)},
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeProject},
		Status:     []string{entity.RoleBindingStatusActive},
	}

	roleBindings, err := svc.ListRoleBindings(ctx, filter)

	if err != nil {
		if err == gorm.ErrRecordNotFound {
			tilog.Info(ctx, "user is not belong to any  active project", zap.Any("userID", userId))
		}
		return nil, err
	}

	if len(roleBindings) == 1 {
		projectID := &roleBindings[0].ScopeID
		tilog.Info(ctx, "user is only one project", zap.Any("userID", userId), zap.Any("projectId", projectID))
		return projectID, nil

	}
	tilog.Info(ctx, "user is not a new user ", zap.Any("userID", userId), zap.Any("project num", len(roleBindings)))
	return nil, nil
}

// PublishRegisterEvent
//
//	@Description: Publish the event
//	@Author wangletian 2023-01-12 20:54:52
//	@param ctx
//	@param auth0Token
//	@param userId
//	@param email
//	@return error
func PublishRegisterEvent(ctx context.Context, q UserQuery, channel *bool) error {
	if config.IsLocalEnv() {
		tilog.Info(ctx, "PublishRegisterEvent skip"+config.GetEnv())
		return nil
	}

	user, err := GetUser(ctx, q)
	if err != nil {
		tilog.Error(ctx, "GetUser", zap.Error(err))
		return err
	}
	orgSvc, _ := orgsvc.GetOrgSvc()
	//ListOrgsByUserID
	orgs, err := orgSvc.ListOrgsByUserID(ctx, user.ID.Uint64())
	tilog.Info(ctx, "query user orgs ", zap.Any("orgs", orgs))
	if err != nil {
		tilog.Error(ctx, "GetUserOrg", zap.Error(err))
	}

	if len(orgs) == 1 {
		org := orgs[0]
		//  if user register from  gcp marketplace, skip
		if org.ThirdPartyAccountProvider != nil && *org.ThirdPartyAccountProvider == handler.GCPAccountProvider {
			tilog.Info(ctx, "PublishRegisterEvent skip, this new user register from  gcp marketplace", zap.Any("user.Email ", user.Email), zap.Any("org.id ", org.ID))
			return nil
		}

		tilog.Info(ctx, "org  ", zap.Any("org.Email ", org.Email), zap.Any("sub CreatedAt ", time.Since(org.CreatedAt).Seconds()))
		if user.Email == org.Email && time.Since(org.CreatedAt).Hours() < tokenExpire {
			// new user
			userLandingAction := &entity.UserLandingAction{
				UserID:       user.ID,
				TargetAction: entity.NewUserAction}
			userSvc, _ := usersvc.GetUserSvc()
			if err := userSvc.CreateUserLandingAction(ctx, userLandingAction); err != nil {
				tilog.Error(ctx, "CreateUserLandingAction fail ", zap.Any("userLandingAction", userLandingAction), zap.Error(err))
			}
			tilog.Info(ctx, "save new user in  UserLandingAction ", zap.Any("userLandingAction", userLandingAction))
		}
	}
	if err != nil {
		tilog.Error(ctx, "GetUser fail", zap.Error(err), zap.Any("q", q))
		return err
	}
	data := RegisterEvent{
		UserId:    user.ID.Uint64(),
		Email:     user.Email,
		EventTime: time.Now(),
		User:      *user,
		ClientIP:  q.ClientIP,
	}

	if channel != nil {
		data.Channel = *channel
	}

	userEvent := &eventbus.Event{
		EventID:   config.Group,
		EventType: config.EventTypeRegister,
		EventData: data,
	}
	tilog.Info(ctx, "PublishRegisterEvent to eventbus", zap.Any("userEvent", userEvent))
	if err = eventbus.Get().PublishEvent(config.EventTypeRegister, userEvent); err != nil {
		tilog.Error(ctx, "PublishRegisterEvent err", zap.Any("event", userEvent))
		return err
	}
	tilog.Info(ctx, "PublishRegisterEvent success", zap.Any("event", userEvent.EventData))
	return nil

}

func GetUser(ctx context.Context, q UserQuery) (*entity.User, error) {
	userSvc, _ := usersvc.GetUserSvc()
	var user *entity.User
	var err error
	if q.UserId != 0 {
		user, err = userSvc.GetUserByID(ctx, q.UserId)
		if err != nil {
			tilog.Info(ctx, "failed to get user by userId", zap.Error(err), zap.Any("err", err.Error()))
			return nil, err
		}
	} else if q.Token != "" {
		tokenSvc, _ := token.GetTokenSvc()
		email, err := tokenSvc.GetUserEmailByToken(ctx, q.Token)
		if err != nil {
			tilog.Info(ctx, "failed to get user email by token", zap.Error(err))
			return nil, err
		}
		user, err = userSvc.GetUserByEmail(ctx, email)
		if err != nil {
			tilog.Info(ctx, "failed to get user email", zap.Error(err))
			return nil, err
		}

	} else if q.Email != "" {
		user, err = userSvc.GetUserByEmail(ctx, q.Email)
		if err != nil {
			tilog.Info(ctx, "failed to get user email", zap.Error(err))
			return nil, err
		}
	}
	return user, nil
}
func callCMCreateCluster(ctx context.Context, userId uint64, orgId uint64, projectId uint64, clientIP string) (clusterId *uint64, err error) {

	region, err := infra.GetHandler().GetServerlessDefaultRegion(ctx, provider, orgId, projectId, clientIP)
	if err != nil {
		return nil, err
	}
	tilog.Info(ctx, "call CM GetCloudRegion ", zap.Any("region", region), zap.String("client_ip", clientIP))

	req := &core.CreateClusterReq{
		TenantId:         orgId,
		ProjectId:        projectId,
		Name:             clusterName,
		CloudProvider:    provider,
		Region:           *region,
		IsDevTier:        true,
		CreateById:       userId,
		DeployType:       core.DeployType_DEPLOY_TYPE_SHARED,
		XaccountDeployId: 0,
		Components: []*core.ClusterComponent{
			{
				ComponentType:   core.ComponentType_COMPONENT_TYPE_TIFLASH,
				NodeProfileName: "Shared0",
				Replicas:        1,
				StorageSizeGi:   1,
			},
			{
				ComponentType:   core.ComponentType_COMPONENT_TYPE_TIDB,
				NodeProfileName: "Shared0",
				Replicas:        1,
				StorageSizeGi:   0,
			},
			{

				ComponentType:   core.ComponentType_COMPONENT_TYPE_TIKV,
				NodeProfileName: "Shared0",
				Replicas:        1,
				StorageSizeGi:   1,
			},
		},
	}
	return infra.GetHandler().CreateCluster(ctx, req)

}

type UserQuery struct {
	UserId   uint64
	Email    string
	Token    string
	ClientIP string
}

type RegisterEvent struct {
	User      entity.User
	EventTime time.Time
	Channel   bool
	UserId    uint64
	Email     string
	ClientIP  string
}
