package user

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/account/internel/dao/entity"
	dbuser2 "github.com/tidbcloud/account/internel/svc/dbuser"
	"github.com/tidbcloud/account/internel/svc/sqluser"
	usersvc "github.com/tidbcloud/account/internel/svc/user"
	"github.com/tidbcloud/tiinf/sdk/auditlog"

	"github.com/tidbcloud/tiinf/errs"

	"github.com/tidbcloud/account/central/utils"
	"go.uber.org/zap"

	"github.com/tidbcloud/tiinf/monitor/tilog"

	"google.golang.org/protobuf/types/known/emptypb"

	"github.com/tidbcloud/account/idl/pbgen/proto/central"
)

const (
	contextTimeoutMsg = "context deadline exceeded"
)

func convertError(err error) error {
	if err == nil {
		return nil
	}
	if strings.Contains(err.Error(), contextTimeoutMsg) {
		return utils.ConvertErr2GrpcErr(errs.NewRequestTimeout(contextTimeoutMsg, err))
	}
	return err
}

func (s *UserService) CreateSqlUser(ctx context.Context, req *central.CreateSqlUserReq) (*emptypb.Empty, error) {

	//tilog.Info(ctx, "CreateSQLUser", zap.Any("req", req))
	tilog.Info(ctx, "CreateSQLUser param:"+fmt.Sprintf("Username=%s,ClusterId=%d,BuiltinRole=%s,CustomRoles=%s", req.UserName, req.ClusterId, req.BuiltinRole, req.CustomRole))
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return &emptypb.Empty{}, err
	}

	if err := validCreateReq(ctx, req); err != nil {
		return &emptypb.Empty{}, err
	}

	svc, _ := sqluser.GetSqlUserSvc()

	operator, err := getCmOperator(ctx, req.OrgId, req.ClusterId)
	if err != nil {
		tilog.Info(ctx, "getCmOperator err", zap.Error(err))
		return nil, err
	}

	err = svc.CreateSqlUserToCm(ctx, req, *operator)
	tilog.Info(ctx, "CreateSQLUser result", zap.Any("err", err), zap.Error(err))

	details := auditlog.Details{
		auditlog.KeyDescription:     "CreateSqlUser",
		auditlog.KeyTargetClusterId: req.ClusterId,
		auditlog.KeySqlUserName:     req.UserName,
		auditlog.KeyError:           err,
	}
	auditlog.NewEvent(ctx, auditlog.EventCreateSQLUser).SetOrgID(req.OrgId).SetDetails(details.Build()).SetOperatorEmail(user.Email).SetResult(err == nil).Submit() //nolint:errcheck
	return &emptypb.Empty{}, convertError(err)
}

func (s *UserService) DeleteSqlUser(ctx context.Context, req *central.DeleteSqlUserReq) (*emptypb.Empty, error) {

	tilog.Info(ctx, "DeleteSqlUser", zap.Any("req", req))
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return &emptypb.Empty{}, err
	}

	svc, _ := sqluser.GetSqlUserSvc()

	operator, err := getCmOperator(ctx, req.OrgId, req.ClusterId)
	if err != nil {
		tilog.Info(ctx, "getCmOperator err", zap.Error(err))
		return nil, err
	}
	err = svc.DeleteSqlUserToCM(ctx, req.ClusterId, req.UserName, *operator)
	details := auditlog.Details{
		auditlog.KeyDescription:     "DeleteSQLUser",
		auditlog.KeyTargetClusterId: req.ClusterId,
		auditlog.KeySqlUserName:     req.UserName,
		auditlog.KeyError:           err,
	}
	auditlog.NewEvent(ctx, auditlog.EventDeleteSQLUser).SetOrgID(req.OrgId).SetDetails(details.Build()).SetOperatorEmail(user.Email).SetResult(err == nil).Submit() //nolint:errcheck
	return &emptypb.Empty{}, convertError(err)
}

func (s *UserService) UpdateSqlUser(ctx context.Context, req *central.UpdateSqlUserReq) (*emptypb.Empty, error) {

	//tilog.Info(ctx, "UpdateSqlUser", zap.Any("req", req))
	tilog.Info(ctx, "UpdateSqlUser param:"+fmt.Sprintf("Username=%s,ClusterId=%d,BuiltinRole=%s,CustomRole=%s", req.UserName, req.ClusterId, req.BuiltinRole, req.CustomRole))

	tilog.Info(ctx, "CustomRole=", zap.Any("(req.CustomRole == nil) ", req.CustomRole == nil))
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return &emptypb.Empty{}, err
	}

	if err := validUpdateSqlUserReq(ctx, req); err != nil {
		return &emptypb.Empty{}, err
	}

	operatorResp, err := getCmOperator(ctx, req.OrgId, req.ClusterId)
	if err != nil {
		tilog.Info(ctx, "getCmOperator err", zap.Error(err))
		return nil, err
	}
	/*
			request :  CustomRole=[]
		    gin   (req.CustomRole == nil)   =  false
		    grpc   (req.CustomRole == nil)  =  true

		    request :  do not set the parameter   CustomRole  , CustomRole=nil
		    gin   (req.CustomRole == nil)   =  true
		    grpc   (req.CustomRole == nil)  =  true
	*/
	svc, _ := sqluser.GetSqlUserSvc()
	updateCustomRoleFlag := true
	err = svc.UpdateSqlUserToCM(ctx, req, updateCustomRoleFlag, *operatorResp)
	m := fmt.Sprintf("BuiltinRole=%s", req.BuiltinRole)
	fields := []string{m}

	if req.Password != "" {
		str := fmt.Sprintf("Password=%s", "******")
		fields = append(fields, str)
	}
	if len(req.CustomRole) > 0 {
		str := fmt.Sprintf("CustomRoles=%s", req.CustomRole)
		fields = append(fields, str)
	}

	details := auditlog.Details{
		auditlog.KeyDescription:     "UpdateSQLUser",
		auditlog.KeyTargetClusterId: req.ClusterId,
		auditlog.KeySqlUserName:     req.UserName,
		auditlog.KeyNewValue:        fields,
		auditlog.KeyError:           err,
	}
	auditlog.NewEvent(ctx, auditlog.EventUpdateSQLUser).SetOrgID(req.OrgId).SetDetails(details.Build()).SetOperatorEmail(user.Email).SetResult(err == nil).Submit() //nolint:errcheck
	return &emptypb.Empty{}, convertError(err)
}

func (s *UserService) ListSqlUsers(ctx context.Context, req *central.ListSqlUsersReq) (*central.ListSqlUsersResp, error) {

	tilog.Info(ctx, "ListSqlUsers", zap.Any("req", req))
	svc, _ := sqluser.GetSqlUserSvc()

	operator, err := getCmOperator(ctx, req.OrgId, req.ClusterId)
	if err != nil {
		if !errors.Is(err, errs.ErrBadRequest) {
			tilog.Error(ctx, "getCmOperator err", zap.Any("err", err))
		}
		return nil, convertError(err)
	}
	p, err := svc.ListSqlUserFormCM(ctx, req.ClusterId, *operator)
	if err != nil {
		return nil, convertError(err)
	}
	users := p.Items
	resp := &central.ListSqlUsersResp{
		Total: uint32(len(users)),
		Items: utils.Pagination(req.Page, req.PageSize, users).([]*central.SqlUser),
	}
	return resp, err
}

func (s *UserService) QuerySqlUser(ctx context.Context, req *central.QuerySqlUserReq) (*central.QuerySqlUserResp, error) {

	tilog.Info(ctx, "QuerySqlUser", zap.Any("req", req))
	svc, _ := sqluser.GetSqlUserSvc()

	operator, err := getCmOperator(ctx, req.OrgId, req.ClusterId)
	if err != nil {
		if !errors.Is(err, errs.ErrBadRequest) {
			tilog.Error(ctx, "getCmOperator err", zap.Any("err", err))
		}
		return nil, convertError(err)
	}
	rsp, err := svc.QueryFormCM(ctx, req, *operator)
	return rsp, convertError(err)

}
func validCreateReq(ctx context.Context, req *central.CreateSqlUserReq) error {
	msg := ""
	if req.BuiltinRole == "" {
		msg = "builtin role cannot be empty"
	}
	if req.Password == "" {
		msg = "password cannot be empty"

	}
	if req.UserName == "" {
		msg = "user name cannot be empty"

	}
	if len(req.UserName) >= 32 {
		msg = "user name length must be less than 32"
	}
	if msg != "" {
		tilog.Info(ctx, msg)
		return errs.NewBadRequest(msg, nil)
	}

	return nil

}
func validUpdateSqlUserReq(ctx context.Context, req *central.UpdateSqlUserReq) error {
	msg := ""

	if req.UserName == "" {
		msg = "user name cannot be empty"

	}

	if msg != "" {
		tilog.Info(ctx, msg)
		return errs.NewBadRequest(msg, nil)
	}

	return nil

}

/*
func validListSqlUserReq(ctx context.Context, req *central.ListSqlUsersReq) error {
	msg := ""
	if req.Page == 0 {
		msg = "page cannot be empty"
	}
	if req.PageSize == 0 {
		msg = "pageSize cannot be empty"

	}
	if msg != "" {
		tilog.Info(ctx, msg)
		return errs.NewBadRequest(msg, nil)
	}

	return nil

}


func getClusterToken(ctx context.Context, clusterId uint64) (*string, error) {

	var cluster *core.Cluster
	token := ""
	if clusterId != 0 {
		cluster, err := infra.GetHandler().GetClusterById(ctx, clusterId, true, infra.SupportDBUserStatus...)

		if err != nil {
			return token, err
		}
		if cluster == nil {
			tilog.Info(ctx, "cluster is delete or not exist", zap.Any("clusterId", token))
			return token, errs.NewBadRequest("cluster status is not normal", nil)
		}
		if !infra.IsSupportDBUser(ctx, cluster) {
			tilog.Info(ctx, "queryCluster status is error or cluster is delete", zap.Any("clusterId", token))
			return token, errs.NewBadRequest("this cluster version not support SQL User", nil)
		}

	} else {
		return token, errs.NewBadRequest("ClusterId cannot be all empty ", nil)
	}

	return cluster.ServerlessToken, nil
}
*/

func getCmOperator(ctx context.Context, orgId, clusterId uint64) (*dbuser2.GetCMOperatorResp, error) {
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return nil, err
	}

	getCMOperatorReq := dbuser2.GetCMOperatorReq{
		OrgId:        orgId,
		ClusterId:    clusterId,
		Email:        user.Email,
		OperatorType: entity.RoleBindingTargetUser,
	}
	dbuserSvc, _ := dbuser2.GetDBUserSvc()

	return dbuserSvc.GetCMOperator(ctx, getCMOperatorReq)
}
