package tenant

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"path"
	"strconv"
	"strings"
	"time"

	userapiv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/core-server/user/v1"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/devops/api"
	tenant_api "github.com/tidbcloud/devops/api/tenant"
	opent "github.com/tidbcloud/devops/entity/operation"
	tntent "github.com/tidbcloud/devops/entity/tenant"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/clients/accountcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/billingcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	contutil "github.com/tidbcloud/devops/pkg/infra/utils/container"
	opsvc "github.com/tidbcloud/devops/pkg/service/operation"
	opsvcregionaction "github.com/tidbcloud/devops/pkg/service/operation/actions/regionaction"
	opsvctenantaction "github.com/tidbcloud/devops/pkg/service/operation/actions/tenantaction"
	opsvcins "github.com/tidbcloud/devops/pkg/service/operation/instance"
	"github.com/tidbcloud/devops/pkg/service/tenant"

	"github.com/gin-gonic/gin"
	"github.com/go-logr/logr"
	"go.uber.org/dig"
)

type ControllerParam struct {
	dig.In

	Conf             *config.RootConfig
	TenantService    tenant.TenantServiceAPI
	OperationService opsvc.OperationServiceAPI
	CoreSvcCli       *coresvccli.CoreSvcCli
	BillingCli       billingcli.Client
	AccountCli       accountcli.Client
	Logger           logr.Logger
}

// CtlController provide the api to do some operations for tidb cluster
type TenantController struct {
	tenantSvc  tenant.TenantServiceAPI
	opSvc      opsvc.OperationServiceAPI
	coreSvcCli *coresvccli.CoreSvcCli
	billingCli billingcli.Client
	accountCli accountcli.Client
	logger     logr.Logger
	conf       config.TenantServiceConfig
}

func (c *TenantController) Name() string {
	return "TenantController"
}

func NewTenantController(p ControllerParam) (ctrl.Controller, error) {
	return &TenantController{
		tenantSvc:  p.TenantService,
		opSvc:      p.OperationService,
		coreSvcCli: p.CoreSvcCli,
		billingCli: p.BillingCli,
		accountCli: p.AccountCli,
		conf:       p.Conf.Services.Tenant,
		logger:     p.Logger.WithName("TenantController"),
	}, nil
}

func (c *TenantController) RegisterAPI(suite *ctrl.Suite) error {
	r := suite.Router

	group := r.Group("/tenants")
	group.Use(ctrl.TenantScopeMiddleware())
	{
		group.GET("", c.ListTenants)
		group.GET("/:tenant_id", c.GetTenant)
		//group.DELETE("/:tenant_id", c.DeleteTenant)
		group.GET("/reservedCapacityTemplates", c.ListReservedCapacityTemplates)
		group.POST("/reservedCapacityTemplates", c.CreateReservedCapacityTemplate)
		group.GET("/reservedCapacityTemplates/:capacity_template_id", c.GetReservedCapacityTemplate)
		group.PATCH("/reservedCapacityTemplates/:capacity_template_id/status", c.UpdateReservedCapacityTemplateStatus)
		group.DELETE("/reservedCapacityTemplates/:capacity_template_id", c.DeleteReservedCapacityTemplate)
		group.GET("/metricsproxy/*any", c.MetricsProxy)
		group.GET("/metrics/query_range", c.MetricsAdvancedQueryRange)
		group.POST("/:tenant_id/credits", c.AddCredits)
		group.POST("/:tenant_id/credits/internal", c.AddCreditsInternal)
		group.POST("/:tenant_id/billing_config_id/:billing_config_id/recalculate", c.RecalculateBills)
		group.POST("/:tenant_id/billing_config_id/:billing_config_id/invoices/:invoice_id/recalculate", c.RecalculateInvoice)
		group.GET("/:tenant_id/quota", c.ListTenantQuota)
		group.GET("/:tenant_id/audit/settings", c.accountCli.GetProxyHandler(func(ctx *gin.Context) string {
			return fmt.Sprintf("/api/v1/ops-audit/%s/settings", ctx.Param("tenant_id"))
		}))
		group.GET("/:tenant_id/audit/audit_logs", c.accountCli.GetProxyHandler(func(ctx *gin.Context) string {
			return fmt.Sprintf("/api/v1/ops-audit/%s/audit_logs", ctx.Param("tenant_id"))
		}))
		group.GET("/:tenant_id/audit/audit_logs/export", c.accountCli.GetProxyHandler(func(ctx *gin.Context) string {
			return fmt.Sprintf("/api/v1/ops-audit/%s/audit_logs/export", ctx.Param("tenant_id"))
		}))
		group.GET("/:tenant_id/audit/event_types", c.accountCli.GetProxyHandler(func(ctx *gin.Context) string {
			return "/api/v1/ops-audit/event_types" // No tenant ID needed for event types
		}))

		group.POST("/impersonate", c.AddImpersonate)
		group.GET("/impersonate", c.GetImpersonate)
		group.DELETE("/impersonate", c.DeleteImpersonate)

		// operations need approval
		group.POST("/:tenant_id", c.UpdateTenant)
		group.GET("/:tenant_id/accountusers", c.GetTenantUsers)
		group.POST("/:tenant_id/accountusers/:user_id", c.AddUserToTenant)
		group.GET("/:tenant_id/projects/:project_id", c.GetProject)
		group.GET("/:tenant_id/projects/:project_id/accountusers", c.GetProjectUsers)
		group.POST("/:tenant_id/projects/:project_id/accountusers/:user_id", c.AddUserToProject)
		group.DELETE("/:tenant_id/accountusers/:user_id", c.RemoveUserFromTenant)
		group.DELETE("/:tenant_id/projects/:project_id/accountusers/:user_id", c.RemoveUserFromProject)
		group.DELETE("/:tenant_id/projects/:project_id", c.DeleteProject)
		group.DELETE("/:tenant_id", c.DeleteTenant)

		// ops audit log for byoc
		group.GET("/:tenant_id/byoc_regions", c.ListByocRegions)
		group.POST("/:tenant_id/ops_audit_log_settings", c.UpdateRegionOpsAuditLogSettings)
	}

	userGroup := r.Group("/accountusers")
	{
		userGroup.DELETE("/:user_id", c.DeleteAccountUser)
	}

	internalRouter := suite.InternalRouter
	iGroup := internalRouter.Group("/tenants")
	{
		iGroup.POST("/sync", c.SyncTenants)
		// used for account
		iGroup.GET("", c.ListTenantsInternal)
		iGroup.GET("/:tenant_id", c.GetTenantInternal)
	}

	return nil
}

// Proxy metrics API from EE-Prom
func (c *TenantController) MetricsProxy(ctx *gin.Context) {
	proxyPath := ctx.Params.ByName("any")
	target, err := url.Parse(c.conf.O11yEndpoint)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	proxy := &httputil.ReverseProxy{}
	proxy.Rewrite = func(preq *httputil.ProxyRequest) {
		preq.SetURL(target)
		preq.Out.URL.Path = path.Join(c.conf.PrometheusPath, proxyPath)
		preq.Out.Header["X-Forwarded-For"] = preq.In.Header["X-Forwarded-For"]
		preq.SetXForwarded()
	}
	proxy.ModifyResponse = func(resp *http.Response) error {
		resp.Header.Del("Access-Control-Allow-Origin")
		return nil
	}
	proxy.ErrorHandler = func(_ http.ResponseWriter, request *http.Request, err error) {
		c.logger.Error(err, "proxy failed", "host", request.URL.Host, "path", request.URL.Path)
	}
	proxy.ServeHTTP(ctx.Writer, ctx.Request)
}

func (c *TenantController) MetricsAdvancedQueryRange(ctx *gin.Context) {
	req := &tenant_api.MetricsQueryRequest{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.Start > req.End {
		ctrl.FailWithCode(ctx, fmt.Errorf("start time should be less than end time"), api.ErrCodeInvalidArgument)
		return
	}
	stepSecond, err := strconv.ParseInt(req.Step, 10, 64)
	if err != nil {
		stepDuration, err := time.ParseDuration(req.Step)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("step should be a valid duration"), api.ErrCodeInvalidArgument)
			return
		}
		stepSecond = int64(stepDuration.Seconds())
	}
	if req.End-req.Start > 20*stepSecond {
		ctrl.FailWithCode(ctx, fmt.Errorf("query range should be less than 20*step"), api.ErrCodeInvalidArgument)
		return
	}
	resp, err := c.tenantSvc.MetricsAdvancedQueryRange(ctx, req.Query, req.Start, req.End, stepSecond)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	ctrl.Success(ctx, &tenant_api.MetricsQueryResponse{
		Status: "success",
		Data:   resp,
	})
}

// ListTenants
//
//	@Id				ListTenants
//	@Description	list tenants
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			req	query		tenant_api.ListTenantReq	true	"ListTenantReq"
//	@Success		200	{object}	tenant_api.ListTenantResp
//	@Failure		500	{object}	api.BaseResp
//	@Router			/api/v1/devops/tenants [get]
func (c *TenantController) ListTenants(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := tenant_api.ListTenantReq{}
	err := ctx.ShouldBindQuery(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	if !userProfile.AggregatedRoleScope.AllowGlobalRegion {
		if len(req.Region) == 0 {
			ctrl.FailWithCode(ctx, fmt.Errorf("empty region is not allowed"), api.ErrCodePermissionDenied)
			return
		}
		for _, filterRegion := range req.Region {
			find := false
			for _, region := range userProfile.AggregatedRoleScope.AllowedRegions {
				if filterRegion == region {
					find = true
					break
				}
			}
			if !find {
				ctrl.FailWithCode(ctx, fmt.Errorf("region %s is not allowed", filterRegion), api.ErrCodePermissionDenied)
				return
			}
		}
		// TODO: limit sales and sales manager
	}

	resp, total, err := c.tenantSvc.ListTenants(ctx, BuildListTenantsParam(&req))
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	ctrl.Success(ctx, tenant_api.ListTenantResp{
		Total:   total,
		Tenants: contutil.TransformSlice(resp, entityToApi),
	})
}

// ListTenantsInternal
//
//	@Id				ListTenantsInternal
//	@Description	list tenants internal
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			req	query		tenant_api.ListTenantReq	true	"ListTenantReq"
//	@Success		200	{object}	tenant_api.ListTenantResp
//	@Failure		500	{object}	api.BaseResp
//	@Router			/api/v1/devops-internal/tenants [get]
func (c *TenantController) ListTenantsInternal(ctx *gin.Context) {
	req := tenant_api.ListTenantReq{}
	err := ctx.ShouldBindQuery(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp, total, err := c.tenantSvc.ListTenants(ctx, tenant.ListTenantsParam{
		Filter: tenant.ListTenantsFilter{
			Kind:         contutil.TransformSlice(req.Kind, contutil.FromString[tntent.TenantKind]),
			Status:       contutil.TransformSlice(req.Status, contutil.FromString[tntent.TenantStatus]),
			Plan:         contutil.TransformSlice(req.Plan, contutil.FromString[tntent.TenantPlan]),
			Region:       contutil.TransformSlice(req.Region, contutil.FromString[tntent.TenantRegion]),
			Segmentation: contutil.TransformSlice(req.Segmentation, contutil.FromString[tntent.TenantSegmentation]),
		},
		Search:  req.Search,
		Page:    int(req.Page),
		PerPage: int(req.PerPage),
		SortBy:  tenant.SortByType(req.SortBy),
		Order:   tenant.OrderType(req.Order),
	})
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	ctrl.Success(ctx, tenant_api.ListTenantResp{
		Total:   total,
		Tenants: contutil.TransformSlice(resp, entityToApi),
	})
}

// GetTenant
//
//	@Id				GetTenant
//	@Description	get tenant
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string	true	"tenant_id"
//	@Success		200			{object}	tenant_api.GetTenantResp
//	@Failure		500			{object}	api.BaseResp
//	@Router			/api/v1/devops/tenants/{tenant_id} [get]
func (c *TenantController) GetTenant(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	id, err := strconv.ParseUint(ctx.Param("tenant_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	tenant := entityToApi(tenantEntity)

	if !userProfile.AggregatedRoleScope.AllowGlobalRegion {
		find := false
		for _, region := range userProfile.AggregatedRoleScope.AllowedRegions {
			if tenant.Region == tntent.TenantRegion(region) {
				find = true
				break
			}
		}
		if !find {
			ctrl.FailWithCode(ctx, fmt.Errorf("org %d is not allowed", id), api.ErrCodePermissionDenied)
			return
		}
	}
	ctrl.Success(ctx, tenant)
}

// GetTenantInternal
//
//	@Id				GetTenantInternal
//	@Description	get tenant internal
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string	true	"tenant_id"
//	@Success		200			{object}	tenant_api.GetTenantResp
//	@Failure		500			{object}	api.BaseResp
//	@Router			/api/v1/devops-internal/tenants/{tenant_id} [get]
func (c *TenantController) GetTenantInternal(ctx *gin.Context) {
	id, err := strconv.ParseUint(ctx.Param("tenant_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	tenant := entityToApi(tenantEntity)
	ctrl.Success(ctx, tenant)
}

// UpdateTenant
//
//	@Id				UpdateTenant
//	@Description	update tenant
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string						true	"tenant_id"
//	@Param			req			body		tenant_api.UpdateTenantReq	true	"UpdateTenantReq"
//	@Success		200			{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id} [post]
func (c *TenantController) UpdateTenant(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.UpdateTenantReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.TenantID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.Reason == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("reason is empty"), api.ErrCodeInvalidArgument)
		return
	}

	id := req.TenantID

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	if !userProfile.AggregatedRoleScope.AllowGlobalRegion {
		tenant := entityToApi(tenantEntity)
		find := false
		for _, region := range userProfile.AggregatedRoleScope.AllowedRegions {
			if tenant.Region == tntent.TenantRegion(region) {
				find = true
				break
			}
		}
		if !find {
			ctrl.FailWithCode(ctx, fmt.Errorf("org %d is not allowed", id), api.ErrCodePermissionDenied)
			return
		}
	}

	tenantRegion := tenantEntity.GetTenantRegion()
	// redirect to operation service
	// NOTE: must match the operation definition.
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "UpdateTenant",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamTenantID:     strconv.FormatUint(id, 10),
			opent.ParamTenantRegion: string(tenantRegion),
			opent.ParamTenantStatus: req.TenantStatus,
			opent.ParamTenantEmail:  req.TenantEmail,
			opent.ParamTenantType:   req.TenantType,
			opent.ParamSalesOwner:   tenantEntity.SalesOwnerEmail,
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionUpdateTenant.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// DeleteTenant
//
//	@Id				DeleteTenant
//	@Description	delete tenant
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string						true	"tenant_id"
//	@Param			req			body		tenant_api.DeleteTenantReq	true	"DeleteTenantReq"
//	@Success		200			{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id} [delete]
func (c *TenantController) DeleteTenant(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.DeleteTenantReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	id, err := strconv.ParseUint(ctx.Param("tenant_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	if !userProfile.AggregatedRoleScope.AllowGlobalRegion {
		tenant := entityToApi(tenantEntity)
		find := false
		for _, region := range userProfile.AggregatedRoleScope.AllowedRegions {
			if tenant.Region == tntent.TenantRegion(region) {
				find = true
				break
			}
		}
		if !find {
			ctrl.FailWithCode(ctx, fmt.Errorf("org %d is not allowed", id), api.ErrCodePermissionDenied)
			return
		}
	}

	tenantRegion := tenantEntity.GetTenantRegion()
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "DeleteTenant",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamTenantID:     strconv.FormatUint(id, 10),
			opent.ParamTenantRegion: string(tenantRegion),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionDeleteTenant.Name,
				StartPolicy: opent.ActionStartPolicyManual,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// AddImpersonate
//
//	@Id				AddImpersonate
//	@Description	add impersonate
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			req	body		tenant_api.AddImpersonateReq	true	"ImpersonateReq"
//	@Success		200	{object}	tenant_api.AddImpersonateResp
//	@Router			/api/v1/devops/tenants/impersonate [post]
func (c *TenantController) AddImpersonate(ctx *gin.Context) {
	// nolint:all
	ctrl.FailWithCode(ctx, fmt.Errorf("Impersonate feature is temporarily disabled."), api.ErrCodeUnavailable)
	return

	// nolint:all
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.AddImpersonateReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.TenantID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.Reason == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("reason is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.ExpireAt == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("expire_at is empty"), api.ErrCodeInvalidArgument)
		return
	}
	id := req.TenantID

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	if !userProfile.AggregatedRoleScope.AllowGlobalRegion {
		tenant := entityToApi(tenantEntity)
		find := false
		for _, region := range userProfile.AggregatedRoleScope.AllowedRegions {
			if tenant.Region == tntent.TenantRegion(region) {
				find = true
				break
			}
		}
		if !find {
			ctrl.FailWithCode(ctx, fmt.Errorf("org %d is not allowed", id), api.ErrCodePermissionDenied)
			return
		}
	}

	op, err := c.getImpersonateOperationForUser(ctx, userProfile.UserEmail)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	if op != nil {
		ctrl.Fail(ctx, fmt.Errorf("a impersonate operation %d already exists", op.ID))
		return
	}

	tenantRegion := tenantEntity.GetTenantRegion()
	// redirect to operation service
	// NOTE: must match the operation definition.
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "ImpersonateTenant",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamTenantID:     strconv.FormatUint(id, 10),
			opent.ParamTenantRegion: string(tenantRegion),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionAddImpersonate.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
			{
				Name:            opsvctenantaction.ActionDelImpersonate.Name,
				StartPolicy:     opent.ActionStartPolicySchedule,
				StartScheduleAt: &req.ExpireAt,
			},
		},
	}
	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.AddImpersonateResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// GetImpersonate
//
//	@Id				GetImpersonate
//	@Description	get impersonate
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Success		200	{object}	tenant_api.GetImpersonateResp
//	@Router			/api/v1/devops/tenants/impersonate [get]
func (c *TenantController) GetImpersonate(ctx *gin.Context) {
	userEmail, ok := ctx.Get(consts.CtxUserEmail)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	email := userEmail.(string)

	var tidPtr *uint64
	var opID *uint64
	op, err := c.getImpersonateOperationForUser(ctx, email)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	if op != nil {
		opID = &op.ID
		tid, err := strconv.ParseUint(op.Params[opent.ParamTenantID], 10, 64)
		if err != nil {
			ctrl.Fail(ctx, err)
			return
		}
		tidPtr = &tid
	}

	ctrl.Success(ctx, tenant_api.GetImpersonateResp{User: email, InstanceID: opID, TenantID: tidPtr})
}

// DeleteImpersonate
//
//	@Id				DeleteImpersonate
//	@Description	delete impersonate
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Success		200	{object}	api.BaseResp
//	@Failure		500	{object}	api.BaseResp
//	@Router			/api/v1/devops/tenants/impersonate [delete]
func (c *TenantController) DeleteImpersonate(ctx *gin.Context) {
	userEmail, ok := ctx.Get(consts.CtxUserEmail)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	email := userEmail.(string)

	// list all impersonate operations to cancel
	op, err := c.getImpersonateOperationForUser(ctx, email)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	if op != nil {
		err := c.opSvc.CancelInstance(ctx, op.ID)
		if err != nil {
			c.logger.Error(err, "cancel impersonate operation failed", "operation_instance", op.ID)
		}
	}

	err = c.tenantSvc.DeleteImpersonate(ctx, email)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	ctrl.Success(ctx, api.BaseResp{})
}

// GetTenantUsers
//
//	@Id				GetTenantUsers
//	@Description	get tenant users
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string					true	"tenant_id"
//	@Param			req			query		tenant_api.GetUsersReq	true	"GetUsersReq"
//	@Success		200			{object}	tenant_api.GetUsersResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/accountusers [get]
func (c *TenantController) GetTenantUsers(ctx *gin.Context) {
	id := ctx.Param("tenant_id")
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.GetUsersReq{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp, err := c.tenantSvc.GetTenantUsers(ctx, id, accountcli.GetUsersReq{
		Search:    req.Search,
		Page:      req.Page,
		PageSize:  req.PageSize,
		FullEmail: req.FullEmail,
	}, userProfile.UserEmail)

	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, tenant_api.GetUsersResp{
		Total: resp.Total,
		Items: resp.Items,
	})
}

// GetProject
//
//	@Id				GetProject
//	@Description	get project
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string	true	"tenant_id"
//	@Param			project_id	path		string	true	"project_id"
//	@Success		200			{object}	tenant_api.GetProjectResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/projects/{project_id} [get]
func (c *TenantController) GetProject(ctx *gin.Context) {
	pid, err := strconv.ParseUint(ctx.Param("project_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("project_id is not a int"), api.ErrCodeInvalidArgument)
		return
	}
	resp, err := c.tenantSvc.GetProject(ctx, pid)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, tenant_api.GetProjectResp{
		Project: resp,
	})
}

// GetProjectUsers
//
//	@Id				GetProjectUsers
//	@Description	get project users
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string					true	"tenant_id"
//	@Param			project_id	path		string					true	"project_id"
//	@Param			req			query		tenant_api.GetUsersReq	true	"request param"
//	@Success		200			{object}	tenant_api.GetUsersResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/projects/{project_id}/accountusers [get]
func (c *TenantController) GetProjectUsers(ctx *gin.Context) {
	oid := ctx.Param("tenant_id")
	pid := ctx.Param("project_id")
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.GetUsersReq{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	resp, err := c.tenantSvc.GetProjectUsers(ctx, oid, pid, accountcli.GetUsersReq{
		Search:    req.Search,
		Page:      req.Page,
		PageSize:  req.PageSize,
		FullEmail: req.FullEmail,
	}, userProfile.UserEmail)

	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, tenant_api.GetUsersResp{
		Total: resp.Total,
		Items: resp.Items,
	})
}

// AddUserToTenant
//
//	@Id				AddUserToTenant
//	@Description	add user to tenant
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string							true	"tenant_id"
//	@Param			user_id		path		string							true	"user_id"
//	@Param			req			body		tenant_api.AddUserToTenantReq	true	"AddUserToTenantReq"
//	@Success		200			{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/accountusers/{user_id} [post]
func (c *TenantController) AddUserToTenant(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.AddUserToTenantReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.TenantID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.UserID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("user_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if len(req.UserRole) == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("user_role is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.Reason == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("reason is empty"), api.ErrCodeInvalidArgument)
		return
	}

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, req.TenantID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	tenantRegion := tenantEntity.GetTenantRegion()
	// redirect to operation service
	// NOTE: must match the operation definition.
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "AddUserToTenant",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamTenantID:     strconv.FormatUint(req.TenantID, 10),
			opent.ParamTenantRegion: string(tenantRegion),
			opent.ParamUserID:       strconv.FormatUint(req.UserID, 10),
			opent.ParamUserEmail:    req.UserEmail,
			opent.ParamUserRole:     req.UserRole,
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionAddUserToTenant.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// AddUserToProject
//
//	@Id				AddUserToProject
//	@Description	add user to project
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string							true	"tenant_id"
//	@Param			project_id	path		string							true	"project_id"
//	@Param			user_id		path		string							true	"user_id"
//	@Param			req			body		tenant_api.AddUserToProjectReq	true	"AddUserToProjectReq"
//	@Success		200			{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/projects/{project_id}/accountusers/{user_id} [post]
func (c *TenantController) AddUserToProject(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.AddUserToProjectReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.TenantID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.ProjectID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("project_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.UserID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("user_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if len(req.UserRole) == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("user_role is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.Reason == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("reason is empty"), api.ErrCodeInvalidArgument)
		return
	}

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, req.TenantID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	tenantRegion := tenantEntity.GetTenantRegion()
	// redirect to operation service
	// NOTE: must match the operation definition.
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "AddUserToProject",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamTenantID:     strconv.FormatUint(req.TenantID, 10),
			opent.ParamTenantRegion: string(tenantRegion),
			opent.ParamProjectID:    strconv.FormatUint(req.ProjectID, 10),
			opent.ParamUserID:       strconv.FormatUint(req.UserID, 10),
			opent.ParamUserEmail:    req.UserEmail,
			opent.ParamUserRole:     req.UserRole,
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionAddUserToProject.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// RemoveUserFromTenant
//
//	@Id				RemoveUserFromTenant
//	@Description	remove user from tenant
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string								true	"tenant_id"
//	@Param			user_id		path		string								true	"user_id"
//	@Param			req			body		tenant_api.RemoveUserFromTenantReq	true	"RemoveUserFromTenantReq"
//	@Success		200			{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/accountusers/{user_id} [delete]
func (c *TenantController) RemoveUserFromTenant(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.RemoveUserFromTenantReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.TenantID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.UserID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("user_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.Reason == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("reason is empty"), api.ErrCodeInvalidArgument)
		return
	}

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, req.TenantID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	tenantRegion := tenantEntity.GetTenantRegion()
	// redirect to operation service
	// NOTE: must match the operation definition.
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "RemoveUserFromTenant",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamTenantID:     strconv.FormatUint(req.TenantID, 10),
			opent.ParamTenantRegion: string(tenantRegion),
			opent.ParamUserID:       strconv.FormatUint(req.UserID, 10),
			opent.ParamUserEmail:    req.UserEmail,
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionRemoveUserFromTenant.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// RemoveUserFromProject
//
//	@Id				RemoveUserFromProject
//	@Description	remove user from project
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string								true	"tenant_id"
//	@Param			project_id	path		string								true	"project_id"
//	@Param			user_id		path		string								true	"user_id"
//	@Param			req			body		tenant_api.RemoveUserFromProjectReq	true	"RemoveUserFromTenantReq"
//	@Success		200			{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/projects/{project_id}/accountusers/{user_id} [delete]
func (c *TenantController) RemoveUserFromProject(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.RemoveUserFromProjectReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.TenantID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.ProjectID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("project_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.UserID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("user_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.Reason == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("reason is empty"), api.ErrCodeInvalidArgument)
		return
	}

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, req.TenantID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	tenantRegion := tenantEntity.GetTenantRegion()
	// redirect to operation service
	// NOTE: must match the operation definition.
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "RemoveUserFromProject",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamTenantID:     strconv.FormatUint(req.TenantID, 10),
			opent.ParamTenantRegion: string(tenantRegion),
			opent.ParamProjectID:    strconv.FormatUint(req.ProjectID, 10),
			opent.ParamUserID:       strconv.FormatUint(req.UserID, 10),
			opent.ParamUserEmail:    req.UserEmail,
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionRemoveUserFromProject.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// DeleteAccountUser
//
//	@Id				DeleteAccountUser
//	@Description	delete account user
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			user_id	path		string							true	"user_id"
//	@Param			req		body		tenant_api.DeleteAccountUserReq	true	"DeleteAccountUserReq"
//	@Success		200		{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/accountusers/{user_id} [delete]
func (c *TenantController) DeleteAccountUser(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.DeleteAccountUserReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.UserID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("user_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.Reason == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("reason is empty"), api.ErrCodeInvalidArgument)
		return
	}

	// redirect to operation service
	// NOTE: must match the operation definition.
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetUser,
		Operation: "DeleteUser",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamUserID:    strconv.FormatUint(req.UserID, 10),
			opent.ParamUserEmail: req.UserEmail,
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionDeleteUser.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// DeleteProject
//
//	@Id				DeleteProject
//	@Description	delete project
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string						true	"tenant_id"
//	@Param			project_id	path		string						true	"project_id"
//	@Param			req			body		tenant_api.DeleteProjectReq	true	"DeleteProjectReq"
//	@Success		200			{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/projects/{project_id} [delete]
func (c *TenantController) DeleteProject(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	req := &tenant_api.DeleteProjectReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.TenantID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.ProjectID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("project_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.Reason == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("reason is empty"), api.ErrCodeInvalidArgument)
		return
	}

	id := req.TenantID
	tenantEntity, err := c.tenantSvc.GetTenant(ctx, id)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	tenantRegion := tenantEntity.GetTenantRegion()
	// redirect to operation service
	// NOTE: must match the operation definition.
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "DeleteProject",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamTenantID:     strconv.FormatUint(id, 10),
			opent.ParamTenantRegion: string(tenantRegion),
			opent.ParamProjectID:    strconv.FormatUint(req.ProjectID, 10),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionDeleteProject.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// AddCredits
//
//	@Id				AddCredits
//	@Description	add credits for tenant
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string						true	"tenant_id"
//	@Param			req			body		tenant_api.AddCreditsReq	true	"AddCreditsReq"
//	@Success		200			{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/credits [post]
func (c *TenantController) AddCredits(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)
	tenantID, err := strconv.ParseUint(ctx.Param("tenant_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	req := &tenant_api.AddCreditsReq{}
	err = ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if tenantID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if req.Reason == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("reason is empty"), api.ErrCodeInvalidArgument)
		return
	}

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, tenantID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	if !userProfile.AggregatedRoleScope.AllowGlobalRegion {
		tenant := entityToApi(tenantEntity)
		find := false
		for _, region := range userProfile.AggregatedRoleScope.AllowedRegions {
			if tenant.Region == tntent.TenantRegion(region) {
				find = true
				break
			}
		}
		if !find {
			ctrl.FailWithCode(ctx, fmt.Errorf("org %d is not allowed", tenantID), api.ErrCodePermissionDenied)
			return
		}
	}

	tenantRegion := tenantEntity.CountryRegion
	if tenantEntity.SalesForceRegion != "" {
		tenantRegion = tenantEntity.SalesForceRegion
	}
	unitFilters, err := json.Marshal(req.UnitFilters)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	// redirect to operation service
	// NOTE: must match the operation definition.
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "AddCreditsForTenant",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamTenantID:        strconv.FormatUint(tenantID, 10),
			opent.ParamTenantRegion:    string(tenantRegion),
			opent.ParamDepartment:      req.Department,
			opent.ParamTotalCredits:    req.TotalCredits,
			opent.ParamExpiredDate:     req.ExpiredDate,
			opent.ParamUsage:           req.Usage,
			opent.ParamUnitFilters:     string(unitFilters),
			opent.ParamDiscountEnabled: strconv.FormatBool(req.DiscountEnabled),
			opent.ParamApplyForTest:    strconv.FormatBool(req.ApplyForTest),
			opent.ParamUseCase:         req.UseCase,
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionCreateCredits.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// AddCreditsInternal
//
//	@Id				AddCreditsInternal
//	@Description	add credits for tenant directly without operation workflow
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string								true	"tenant_id"
//	@Param			req			body		tenant_api.AddCreditsInternalReq	true	"AddCreditsInternalReq"
//	@Success		200			{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/credits/internal [post]
func (c *TenantController) AddCreditsInternal(ctx *gin.Context) {
	if !allowDirectCreditCaller(ctx) {
		ctrl.FailWithCode(ctx, fmt.Errorf("only user access token or user api token can access internal tenant credit api"), api.ErrCodePermissionDenied)
		return
	}

	tenantID, err := strconv.ParseUint(ctx.Param("tenant_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant id is not a int"), api.ErrCodeInvalidArgument)
		return
	}
	if tenantID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is empty"), api.ErrCodeInvalidArgument)
		return
	}

	req := &tenant_api.AddCreditsInternalReq{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	operatorEmail := ctx.GetString(consts.CtxUserEmail)
	if operatorEmail == "" {
		operatorEmail = ctx.GetString(consts.CtxOperator)
	}
	if operatorEmail == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("user email not found in auth context"), api.ErrCodePermissionDenied)
		return
	}

	unitFilters := make([]*billingcli.UnitFilterSlim, 0, len(req.UnitFilters))
	for _, item := range req.UnitFilters {
		unitFilters = append(unitFilters, &billingcli.UnitFilterSlim{
			FilterID: item.FilterID,
			Name:     item.Name,
			TypeID:   item.TypeID,
		})
	}

	createReq := &billingcli.CreateCreditsPackageReq{
		StartDate:       req.StartDate,
		ExpiredDate:     req.ExpiredDate,
		TotalCredits:    req.TotalCredits,
		Usage:           req.Usage,
		Note:            req.Note,
		UnitFilters:     unitFilters,
		DiscountEnabled: req.DiscountEnabled,
		ForeignID:       req.ForeignID,
		UserEmail:       operatorEmail,
	}
	if err := c.billingCli.CreateCredits(ctx, tenantID, createReq); err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// RecalculateBills
//
//	@Id				RecalculateBills
//	@Description	create an approval operation to recalculate bills for a tenant billing config
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id			path		string							true	"tenant_id"
//	@Param			billing_config_id	path		string							true	"billing_config_id"
//	@Param			req					body		tenant_api.RecalculateBillsReq	true	"RecalculateBillsReq"
//	@Success		200					{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/billing_config_id/{billing_config_id}/recalculate [post]
func (c *TenantController) RecalculateBills(ctx *gin.Context) {
	req := &tenant_api.RecalculateBillsReq{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	sourceRunID := strings.TrimSpace(req.SourceRunID)
	if req.OverwriteCurrentCost || strings.TrimSpace(req.CreditsTotalCredits) == "" {
		if sourceRunID == "" {
			ctrl.FailWithCode(ctx, fmt.Errorf("source_run_id is required"), api.ErrCodeInvalidArgument)
			return
		}
	}

	instanceID, err := c.createBillsRecalculateOperation(ctx, "", sourceRunID, req.OverwriteCurrentCost, req.RecalculateCreditsReq, req.Reason, "RecalculateBillsForTenantBillingConfig")
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &tenant_api.CommonResp{InstanceID: instanceID})
}

// RecalculateInvoice
//
//	@Id				RecalculateInvoice
//	@Description	create an approval operation to recalculate a tenant invoice
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id			path		string								true	"tenant_id"
//	@Param			billing_config_id	path		string								true	"billing_config_id"
//	@Param			invoice_id			path		string								true	"invoice_id"
//	@Param			req					body		tenant_api.RecalculateInvoiceReq	true	"RecalculateInvoiceReq"
//	@Success		200					{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/billing_config_id/{billing_config_id}/invoices/{invoice_id}/recalculate [post]
func (c *TenantController) RecalculateInvoice(ctx *gin.Context) {
	req := &tenant_api.RecalculateInvoiceReq{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	invoiceID := strings.TrimSpace(ctx.Param("invoice_id"))
	if invoiceID == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("invoice_id is empty"), api.ErrCodeInvalidArgument)
		return
	}
	if strings.TrimSpace(req.CreditsTotalCredits) == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("credits_total_credits is required"), api.ErrCodeInvalidArgument)
		return
	}

	instanceID, err := c.createBillsRecalculateOperation(ctx, invoiceID, "", false, req.RecalculateCreditsReq, req.Reason, "RecalculateInvoiceForTenantBillingConfig")
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &tenant_api.CommonResp{InstanceID: instanceID})
}

func (c *TenantController) createBillsRecalculateOperation(ctx *gin.Context, invoiceID string, sourceRunID string, overwriteCurrentCost bool, creditsReq tenant_api.RecalculateCreditsReq, reason string, operation string) (*uint64, error) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		return nil, errs.APIErrorNew(api.ErrCodeUnauthenticated, fmt.Errorf("user profile not found"))
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	tenantID, err := strconv.ParseUint(ctx.Param("tenant_id"), 10, 64)
	if err != nil {
		return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, fmt.Errorf("tenant id is not a int"))
	}
	if tenantID == 0 {
		return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, fmt.Errorf("tenant_id is empty"))
	}

	billingConfigID, err := strconv.ParseUint(ctx.Param("billing_config_id"), 10, 64)
	if err != nil {
		return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, fmt.Errorf("billing_config_id is not a int"))
	}
	if billingConfigID == 0 {
		return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, fmt.Errorf("billing_config_id is empty"))
	}
	if strings.TrimSpace(reason) == "" {
		return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, fmt.Errorf("reason is empty"))
	}

	tenantEntity, err := c.tenantSvc.GetTenant(ctx, tenantID)
	if err != nil {
		return nil, err
	}

	if !userProfile.AggregatedRoleScope.AllowGlobalRegion {
		tenant := entityToApi(tenantEntity)
		find := false
		for _, region := range userProfile.AggregatedRoleScope.AllowedRegions {
			if string(tenant.Region) == region {
				find = true
				break
			}
		}
		if !find {
			return nil, errs.APIErrorNew(api.ErrCodePermissionDenied, fmt.Errorf("org %d is not allowed", tenantID))
		}
	}

	params := map[opent.ParamName]string{
		opent.ParamTenantID:        strconv.FormatUint(tenantID, 10),
		opent.ParamBillingConfigID: strconv.FormatUint(billingConfigID, 10),
	}
	if strings.TrimSpace(sourceRunID) != "" {
		params[opent.ParamSourceRunID] = strings.TrimSpace(sourceRunID)
	}
	if invoiceID != "" {
		params[opent.ParamInvoiceID] = invoiceID
	} else {
		params[opent.ParamOverwriteCurrentCost] = strconv.FormatBool(overwriteCurrentCost)
	}
	if !overwriteCurrentCost && strings.TrimSpace(creditsReq.CreditsTotalCredits) != "" {
		if strings.TrimSpace(creditsReq.CreditsExpiredDate) == "" {
			return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, fmt.Errorf("credits_expired_date is required"))
		}
		if strings.TrimSpace(creditsReq.CreditsProvider) == "" {
			return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, fmt.Errorf("credits_provider is required"))
		}
		if strings.TrimSpace(creditsReq.CreditsStartDate) == "" {
			return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, fmt.Errorf("credits_start_date is required"))
		}
		if len(creditsReq.CreditsUnitFilters) == 0 {
			return nil, errs.APIErrorNew(api.ErrCodeInvalidArgument, fmt.Errorf("credits_unit_filters is required"))
		}
		creditsUnitFilters, err := json.Marshal(creditsReq.CreditsUnitFilters)
		if err != nil {
			return nil, err
		}
		params[opent.ParamCreditsTotalCredits] = strings.TrimSpace(creditsReq.CreditsTotalCredits)
		params[opent.ParamCreditsDiscountEnabled] = strconv.FormatBool(creditsReq.CreditsDiscountEnabled)
		params[opent.ParamCreditsExpiredDate] = strings.TrimSpace(creditsReq.CreditsExpiredDate)
		params[opent.ParamCreditsProvider] = strings.TrimSpace(creditsReq.CreditsProvider)
		params[opent.ParamCreditsStartDate] = strings.TrimSpace(creditsReq.CreditsStartDate)
		params[opent.ParamCreditsUnitFilters] = string(creditsUnitFilters)
		params[opent.ParamUsage] = "redeem"
		params[opent.ParamUseCase] = "rerun redeem"
	}

	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: operation,
		Reason:    reason,
		Applicant: userProfile.UserEmail,
		Params:    params,
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvctenantaction.ActionBillsRecalculate.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	return c.opSvc.CreateInstance(ctx, createOPparam)
}

func allowDirectCreditCaller(ctx *gin.Context) bool {
	switch consts.GetOperatorTypeFromCtx(ctx) {
	case consts.CtxOperatorTypeUser, consts.CtxOperatorTypeAPIToken:
		return true
	default:
		return false
	}
}

// ListTenantQuota
//
//	@Id				ListTenantQuota
//	@Description	list tenant quota
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string	true	"tenant_id"
//	@Success		200			{object}	tenant_api.ListTenantQuotaResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/quota [get]
func (c *TenantController) ListTenantQuota(ctx *gin.Context) {
	tenantID, err := strconv.ParseUint(ctx.Param("tenant_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	if tenantID == 0 {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is empty"), api.ErrCodeInvalidArgument)
		return
	}

	quota, err := c.coreSvcCli.Core.ListTenantQuota(ctx, &core.ListTenantQuotaReq{TenantId: tenantID})
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := tenant_api.BuildTenantQuotas(quota.TenantQuotas)
	ctrl.Success(ctx, resp)
}

// UpdateRegionOpsAuditLogSettings
//
//	@Id				UpdateRegionOpsAuditLogSettings
//	@Description	update region ops audit log settings
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string											true	"tenant_id"
//	@Param			req			body		tenant_api.UpdateRegionOpsAuditLogSettingReq	true	"UpdateRegionOpsAuditLogSettingReq"
//	@Success		200			{object}	tenant_api.CommonResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/ops_audit_log_settings [post]
func (c *TenantController) UpdateRegionOpsAuditLogSettings(ctx *gin.Context) {
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)
	tenantID, err := strconv.ParseUint(ctx.Param("tenant_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	req := &tenant_api.UpdateRegionOpsAuditLogSettingReq{}
	err = ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	opReq := coresvccli.UpdateRegionOpsAuditLogSettingReq{
		RegionLogicalName: req.RegionLogicalName,
	}

	if req.Enabled != nil {
		opReq.Enabled = *req.Enabled
	}
	if req.LogLocation != nil {
		opReq.LogLocation = &coresvccli.LogLocation{
			BucketUri:  req.LogLocation.BucketUri,
			RoleArn:    req.LogLocation.RoleArn,
			AccountId:  req.LogLocation.AccountId,
			ExternalId: req.LogLocation.ExternalId,
		}
	}
	if req.LogRotation != nil {
		opReq.LogRotation = &coresvccli.LogRotation{
			FileSizeLimitInMb:     req.LogRotation.FileSizeLimitInMb,
			RotationIntervalInMin: req.LogRotation.RotationIntervalInMin,
		}
	}

	opReqData, err := json.Marshal(opReq)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("marshal operation request failed: %s", err), api.ErrCodeInternal)
		return
	}
	// redirect to operation service
	// NOTE: must match the operation definition.
	createOpParam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetRegion,
		Operation: "UpdateRegionOpsAuditLogSetting",
		Reason:    req.Reason,
		Applicant: userProfile.UserEmail,
		Params: map[opent.ParamName]string{
			opent.ParamTenantID: strconv.FormatUint(tenantID, 10),
			opent.ParamUpdateRegionOpsAuditLogSettingRequest: string(opReqData),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvcregionaction.ActionUpdateRegionOpsAuditLogSetting.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOpParam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &tenant_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// ListByocRegions
//
//	@Id				ListByocRegions
//	@Description	list byoc regions for a tenant
//	@Tags			TenantService
//	@Accept			json
//	@Produce		json
//	@Param			tenant_id	path		string	true	"tenant_id"
//	@Success		200			{object}	tenant_api.ListByocRegionsResp
//	@Router			/api/v1/devops/tenants/{tenant_id}/byoc_regions [get]
func (c *TenantController) ListByocRegions(ctx *gin.Context) {
	tenantID, err := strconv.ParseUint(ctx.Param("tenant_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id is not a int"), api.ErrCodeInvalidArgument)
		return
	}

	regions, err := c.coreSvcCli.Mgmt.ListByocRegions(ctx, tenantID)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("list regions failed: %s", err), api.ErrCodeInternal)
		return
	}

	resp := tenant_api.BuildRegions(regions)
	ctrl.Success(ctx, resp)
}

func (c *TenantController) SyncTenants(ctx *gin.Context) {
	c.tenantSvc.SyncTenants(ctx)
	ctrl.Success(ctx, api.EmptyResp{})
}

func (c *TenantController) getImpersonateOperationForUser(ctx context.Context, email string) (*opent.Instance, error) {
	// list all impersonate operations to cancel
	opListParam := opsvc.ListInstanceParam{
		Applicants: []string{email},
		Operations: []string{"ImpersonateTenant"},
		PhasesNot:  opent.InstancePhaseFinishedValues,
	}
	opList, _, err := c.opSvc.ListInstances(ctx, opListParam)
	if err != nil {
		return nil, err
	}
	if len(opList) == 0 {
		return nil, nil
	}
	return opList[0], nil
}

func entityToApi(tenant *tntent.Tenant) tenant_api.TenantResponse {
	region := tenant.GetTenantRegion()
	kind := tenant.Kind
	if tenant.SalesForceKind != "" {
		kind = tenant.SalesForceKind
	}

	return tenant_api.TenantResponse{
		Id:              tenant.Id,
		CreatedAt:       tenant.CreatedAt,
		Name:            tenant.Name,
		Alias:           tenant.Alias,
		Email:           maskEmail(tenant.Email),
		Kind:            kind,
		SalesOwnerEmail: tenant.SalesOwnerEmail,
		Region:          region,
		Country:         tenant.Country,
		TimeZone:        tenant.TimeZone,
		Plan:            tenant.Plan,
		Status:          tenant.Status,
		CustomerID:      tenant.CustomID,
		Segmentation:    tenant.Segmentation,
		TAMName:         tenant.TAMName,
	}
}

func BuildListTenantsParam(req *tenant_api.ListTenantReq) tenant.ListTenantsParam {
	return tenant.ListTenantsParam{
		Filter: tenant.ListTenantsFilter{
			Kind:         contutil.TransformSlice(req.Kind, contutil.FromString[tntent.TenantKind]),
			Status:       contutil.TransformSlice(req.Status, contutil.FromString[tntent.TenantStatus]),
			Plan:         contutil.TransformSlice(req.Plan, contutil.FromString[tntent.TenantPlan]),
			Region:       contutil.TransformSlice(req.Region, contutil.FromString[tntent.TenantRegion]),
			Segmentation: contutil.TransformSlice(req.Segmentation, contutil.FromString[tntent.TenantSegmentation]),
		},
		Search:  req.Search,
		Page:    int(req.Page),
		PerPage: int(req.PerPage),
		SortBy:  tenant.SortByType(req.SortBy),
		Order:   tenant.OrderType(req.Order),
	}
}

func maskEmail(email string) string {
	atIndex := strings.Index(email, "@")
	if atIndex != -1 && atIndex > 0 {
		email = fmt.Sprintf("%s%s%s", string(email[0]), strings.Repeat("*", atIndex-1), email[atIndex:])
	}
	return email
}
