package cloudconfig

import (
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/devops/api"
	ccapi "github.com/tidbcloud/devops/api/cloudconfig"
	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	opent "github.com/tidbcloud/devops/entity/operation"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/clients/configcentercli"
	coresvccli "github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	ccsvcapi "github.com/tidbcloud/devops/pkg/service/cloudconfig/api"
	opsvc "github.com/tidbcloud/devops/pkg/service/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions/ngoperation"
	opsvcopsaction "github.com/tidbcloud/devops/pkg/service/operation/actions/opsaction"
	opsvcins "github.com/tidbcloud/devops/pkg/service/operation/instance"
	tntsvc "github.com/tidbcloud/devops/pkg/service/tenant"

	ccproto "github.com/tidbcloud/config-center/proto/config_center"

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

const updateNgGlobalComponentImageOperation = "UpdateNgGlobalComponentImage"

type ControllerParam struct {
	dig.In

	Conf               *config.RootConfig
	CloudConfigService ccsvcapi.CloudConfigServiceAPI
	OperationService   opsvc.OperationServiceAPI
	TenantService      tntsvc.TenantServiceAPI
	ConfigCenterCli    *configcentercli.Client
	CoreSvcCli         *coresvccli.CoreSvcCli
}

type CloudConfigController struct {
	Conf       *config.RootConfig
	svc        ccsvcapi.CloudConfigServiceAPI
	cfgCenter  *configcentercli.Client
	opSvc      opsvc.OperationServiceAPI
	tenantSvc  tntsvc.TenantServiceAPI
	coreSvcCli *coresvccli.CoreSvcCli
}

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

func NewCloudConfigController(p ControllerParam) ctrl.Controller {
	return &CloudConfigController{
		Conf:       p.Conf,
		svc:        p.CloudConfigService,
		opSvc:      p.OperationService,
		cfgCenter:  p.ConfigCenterCli,
		tenantSvc:  p.TenantService,
		coreSvcCli: p.CoreSvcCli,
	}
}

func (c *CloudConfigController) RegisterAPI(suite *ctrl.Suite) error {
	r := suite.Router
	group := r.Group("/cloudconfig")
	{

		clusterGroup := group.Group("/cluster")
		{
			clusterGroup.GET("/config", c.GetClusterConfiguration)
			clusterGroup.GET("/changes", c.ListClusterConfigurationChanges)
			clusterGroup.POST("/changes", c.CreateClusterConfigurationChange)
			clusterGroup.POST("/changes/preview", c.PreviewClusterConfigurationChange)
		}

		tenantInitGroup := group.Group("/tenantinit")
		{
			tenantInitGroup.GET("/config", c.GetTenantInitConfiguration)
			tenantInitGroup.GET("/changes", c.ListTenantInitConfigurationChanges)
			tenantInitGroup.POST("/changes", c.CreateTenantInitConfigurationChange)
			tenantInitGroup.POST("/changes/preview", c.PreviewTenantInitConfigurationChange)
			tenantInitGroup.GET("/images", c.ListTenantInitImages)
		}

		group.GET("/changes/:change_id", c.GetConfigurationChange)
		group.POST("/changes/:change_id/apply", c.ApplyConfigurationChange)
		group.POST("/changes/:change_id/cancel", c.CancelConfigurationChange)

		tmplG := group.Group("/template")
		{
			tmplG.GET("/templates", c.ListTemplates)
			tmplG.POST("/templates", c.CreateTemplate)
			tmplG.GET("/templates/:template_id", c.GetTemplate)
			tmplG.GET("/templates/:template_id/diff", c.DiffTemplate)
			tmplG.GET("/changes", c.ListConfigurationTemplateChange)
			tmplG.POST("/changes", c.CreateConfigurationTemplateChange)
		}

		dcG := group.Group("/default-config")
		{
			dcG.GET("/template-bindings", c.ListTemplateDefaultCaseBindings)
			dcG.PATCH("/template-bindings", c.UpdateTemplateDefaultCaseBindings)
			dcG.POST("/template-bindings/update-by-op", c.UpdateTemplateDefaultCaseBindingByOp)
			dcG.POST("/template-bindings/delete-by-op", c.DeleteTemplateDefaultCaseBindingByOp)
			dcG.POST("/template-bindings/change", c.ChangeTemplateDefaultCaseBindings)

			dcG.PATCH("/components", c.UpdateDefaultComponents)
			dcG.POST("/components/change", c.ChangeDefaultComponents)

			dcG.GET("/versions/defaultrules", c.ListDefaultVersionRules)
			dcG.POST("/versions/release", c.ReleaseVersion)
			dcG.POST("/versions/import", c.ImportVersion)

			dcG.GET("/changes", c.ListDefaultConfigurationChanges)
			dcG.GET("/changes/:change_id", c.GetDefaultConfigurationChange)
			dcG.POST("/changes/:change_id/close", c.CloseDefaultConfigurationChange)

			iG := dcG.Group("/changes/:change_id/items")
			{
				iG.GET("", c.ListDefaultConfigurationChangeItems)
				iG.GET("/unreleased", c.ListUnreleasedDefaultConfigurationChangeItems)
				iG.POST("/batchUpdate", c.BatchUpdateDefaultConfigurationChangeItems)
				iG.GET("/stat", c.StatDefaultConfigurationChangeItems)
			}

			rg := dcG.Group("/changes/:change_id/releases")
			{
				rg.GET("", c.ListDefaultConfigurationChangeReleases)
				rg.POST("", c.CreateDefaultConfigurationChangeRelease)
				rg.POST("/rollback", c.RollbackDefaultConfigurationChangeRelease)
				rg.GET("/:release_id", c.GetDefaultConfigurationChangeRelease)
				rg.GET("/:release_id/items", c.ListDefaultConfigurationChangeItemRelease)
				rg.GET("/:release_id/cancel", c.CancelDefaultConfigurationChangeRelease)
			}
		}

		group.GET("/parameter/versions", c.ListParameterVersions)
		group.GET("/parameter/attribute/:configtype/:version", c.ListParameterAttributes)
		group.GET("/parameter/attribute/:configtype/:version/:param_name", c.GetParameterAttribute)

		group.GET("/config-types/:cluster_type", c.ListConfigTypes)
		group.GET("/components", c.ListComponents)
		group.GET("/cluster-components/search", c.SearchClusterComponents)

		group.GET("/versions/cluster/:cluster_type", c.ListAvailableClusterVersions)
		group.PATCH("/versions/cluster/:cluster_type/:version", c.UpdateDefaultClusterVersion)
		group.GET("/versions/component/:component/update-records", c.ListComponentImageUpdateRecords)
		group.GET("/versions/component/:component", c.ListAvailableComponentVersions)
		group.POST("/versions/component/:component", c.UpdateComponentImage)

		group.GET("/instance-types/:cluster_type", c.ListInstanceTypes)

	}

	admgroup := r.Group("/cloudconfig-admin") // sperate admin api to make acess control easier
	{
		admgroup.PUT("/parameter/attribute/:config_type/:version/:param_name", c.UpdateParameterAttribute)
		admgroup.POST("/parameter/attribute/create", c.CreateParameterAttribute)
		admgroup.POST("/parameter/attribute/clone", c.CloneParameterAttribute)
	}

	ticdcgroup := r.Group("/ticdc-meta")
	{
		ticdcgroup.GET("/:cluster_type/internal-changefeeds/:cluster_id", c.ListInternalChangefeeds)
		ticdcgroup.GET("/:cluster_type/internal-changefeed/:changefeed_id", c.GetInternalChangefeed)
		ticdcgroup.GET("/:cluster_type/internal-ticdc/:ticdc_cluster_id", c.GetInternalTiCDCCluster)
		ticdcgroup.POST("/:cluster_type/internal-ticdc/:ticdc_cluster_id", c.RestartTiCDCCluster)
		ticdcgroup.POST("/:cluster_type/internal-changefeed/:changefeed_id", c.RestartChangefeed)
		ticdcgroup.POST("/:cluster_type/internal-changefeed/pause/:changefeed_id", c.PauseChangefeed)
		ticdcgroup.POST("/:cluster_type/internal-changefeed/resume/:changefeed_id", c.ResumeChangefeed)
		ticdcgroup.POST("/internal-ticdc/:ticdc_cluster_id/shutdown", c.ShutdownTiCDCCluster)
	}

	metaprofilegroup := group.Group("/meta_profile")
	{
		metaprofilegroup.GET("/:profile_id", c.GetMetaProfile)
		metaprofilegroup.POST("", c.CreateMetaProfile)
		metaprofilegroup.GET("", c.ListMetaProfiles)
	}
	return nil
}

// ListConfigTypes
//
//	@Id				CloudConfigControllerListConfigTypes
//	@Description	list available config types
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			cluster_type	path		ccent.ClusterType	true	"cluster_type"
//	@Success		200				{object}	ccapi.ListConfigTypesResp
//	@Router			/api/v1/devops/cloudconfig/config-types/{cluster_type} [get]
func (c *CloudConfigController) ListConfigTypes(ctx *gin.Context) {
	typ := ctx.Param("cluster_type")
	if typ == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("cluster_type is required"), api.ErrCodeInvalidArgument)
		return
	}
	configTypes := ccproto.ClusterType(ccproto.ClusterType_value[typ]).AvailableConfigTypes()

	resp := &ccapi.ListConfigTypesResp{
		ConfigTypes: configTypes,
	}
	ctrl.Success(ctx, resp)
}

// ListComponents
//
//	@Id				CloudConfigControllerListComponents
//	@Description	list available components
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			cluster_type	query		ccent.ClusterType	true	"cluster_type"
//	@Success		200				{object}	ccapi.ListComponentsResp
//	@Router			/api/v1/devops/cloudconfig/components [get]
func (c *CloudConfigController) ListComponents(ctx *gin.Context) {
	typ := ctx.Query("cluster_type")
	if typ == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("cluster_type is required"), api.ErrCodeInvalidArgument)
		return
	}
	components := ccproto.ClusterType(ccproto.ClusterType_value[typ]).AvailableComponents()

	resp := &ccapi.ListComponentsResp{
		Components: components,
	}
	ctrl.Success(ctx, resp)
}

// SearchClusterComponents
//
//	@Id				CloudConfigControllerSearchClusterComponents
//	@Description	search cluster component versions
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		query		ccapi.SearchClusterComponentsReq	true	"request param"
//	@Success		200		{object}	ccapi.SearchClusterComponentsResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/cluster-components/search [get]
func (c *CloudConfigController) SearchClusterComponents(ctx *gin.Context) {
	req := &ccapi.SearchClusterComponentsReq{}
	if err := ctx.ShouldBindQuery(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.TenantID != "" && req.ClusterID != "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id and cluster_id are mutually exclusive"), api.ErrCodeInvalidArgument)
		return
	}

	var tenantID *uint64
	if req.TenantID != "" {
		id, err := strconv.ParseUint(req.TenantID, 10, 64)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("invalid tenant_id %q: %w", req.TenantID, err), api.ErrCodeInvalidArgument)
			return
		}
		if err := ctrl.CheckTenantScope(ctx, id); err != nil {
			ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
			return
		}
		tenantID = &id
	}
	if req.ClusterID != "" {
		if err := c.checkClusterLocatorScope(ctx, req.ClusterID); err != nil {
			ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
			return
		}
	}
	if tenantID == nil && req.ClusterID == "" && ctrl.IsTenantRestricted(ctx) {
		ctrl.FailWithCode(ctx, fmt.Errorf("tenant_id or cluster_id is required for this api key"), api.ErrCodePermissionDenied)
		return
	}

	result, err := c.svc.SearchClusterComponents(ctx, ccsvcapi.SearchClusterComponentsParam{
		TenantID:         tenantID,
		ClusterID:        req.ClusterID,
		ClusterType:      req.ClusterType,
		ComponentName:    req.ComponentName,
		ComponentVersion: req.ComponentVersion,
		ImageTag:         req.ImageTag,
		LogicalRegion:    req.LogicalRegion,
		RegionMode:       req.RegionMode,
		Page:             req.Page,
		PerPage:          req.PerPage,
	})
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.SearchClusterComponentsResp{
		Total:          result.Total,
		Items:          result.Items,
		RegionResults:  result.RegionResults,
		PartialFailure: result.PartialFailure,
	}
	ctrl.Success(ctx, resp)
}

// ListAvailableComponentVersions
//
//	@Id				CloudConfigControllerListAvailableComponentVersions
//	@Description	list available component versions
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			component		path		string				true	"component"
//	@Param			cluster_type	query		ccent.ClusterType	false	"cluster_type, default is DEDICATED_TIDB"
//	@Param			tenant_id		query		string				false	"tenant_id, optional"
//	@Success		200				{object}	ccapi.ListAvailableComponentVersionsResp
//	@Router			/api/v1/devops/cloudconfig/versions/component/{component} [get]
func (c *CloudConfigController) ListAvailableComponentVersions(ctx *gin.Context) {
	comp := ctx.Param("component")
	if comp == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("component is required"), api.ErrCodeInvalidArgument)
		return
	}
	if comp == string(ccent.ClusterTypeDedicatedTiDB) {
		comp = "tidb"
	}
	clusterType := ctx.Query("cluster_type")
	if clusterType == "" {
		clusterType = string(ccent.ClusterTypeDedicatedTiDB)
	}
	tenantIDStr := ctx.Query("tenant_id")
	if tenantIDStr == "" {
		tenantIDStr = "0"
	}

	versions, err := c.svc.ListAvailableComponentVersions(ctx, ccent.ClusterType(clusterType), comp)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	req := &ccproto.ListComponentImagesRequest{
		ClusterType:   ccproto.ClusterType(ccproto.ClusterType_value[clusterType]),
		ComponentName: comp,
	}
	tenantID, err := strconv.ParseUint(tenantIDStr, 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	req.TenantId = &tenantID
	listResp, err := c.cfgCenter.ConfigAPI.ListComponentImages(ctx, req)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	var detail []ccapi.ComponentInfo
	for _, v := range listResp.Images {
		detail = append(detail, ccapi.ComponentInfo{
			ComponentName:       v.ComponentName,
			Version:             v.Version,
			BaseImage:           v.BaseImage,
			ImageTag:            v.Tag,
			GeneralAvailability: v.GeneralAvailability,
		})
	}

	resp := &ccapi.ListAvailableComponentVersionsResp{
		Versions: versions,
		Detail:   detail,
	}
	ctrl.Success(ctx, resp)
}

// ListComponentImageUpdateRecords
//
//	@Id				CloudConfigControllerListComponentImageUpdateRecords
//	@Description	list component image update operation records
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			component	path		string										true	"component"
//	@Param			req			query		ccapi.ListComponentImageUpdateRecordsReq	false	"request param"
//	@Success		200			{object}	ccapi.ListComponentImageUpdateRecordsResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/versions/component/{component}/update-records [get]
func (c *CloudConfigController) ListComponentImageUpdateRecords(ctx *gin.Context) {
	component := strings.TrimSpace(ctx.Param("component"))
	if component == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("component is required"), api.ErrCodeInvalidArgument)
		return
	}

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

	paramSearches := map[opent.ParamName]string{}
	clusterType := normalizeComponentImageRecordClusterType(req.ClusterType)
	if clusterType != "" {
		paramSearches[opent.ParamCloudConfigClusterType] = string(clusterType)
	}
	paramSearches[opent.ParamNgModifyImageComponent] = component

	param := opsvc.ListInstanceParam{
		Operations: []string{updateNgGlobalComponentImageOperation},
		Page:       int(req.Page),
		PerPage:    int(req.PerPage),
	}
	if len(paramSearches) > 0 {
		param.ParamSearches = paramSearches
	}

	insList, total, err := c.opSvc.ListInstances(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.ListComponentImageUpdateRecordsResp{
		Total:     total,
		Instances: insList,
	}
	ctrl.Success(ctx, resp)
}

func normalizeComponentImageRecordClusterType(clusterType ccent.ClusterType) ccent.ClusterType {
	normalized := strings.TrimSpace(string(clusterType))
	if normalized == "" {
		return ""
	}
	normalized = strings.ReplaceAll(normalized, "-", "_")
	return ccent.ClusterType(strings.ToUpper(normalized))
}

// UpdateComponentImage
//
//	@Id				CloudConfigControllerUpdateComponentImage
//	@Description	update component's image only for ng
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			component	path		string							true	"component"
//	@Param			req			body		ccapi.UpdateComponentImageReq	true	"request param"
//	@Success		200			{object}	cloudconfig.OperationResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/versions/component/{component} [post]
func (c *CloudConfigController) UpdateComponentImage(ctx *gin.Context) {
	comp := ctx.Param("component")
	if comp == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("component is required"), api.ErrCodeInvalidArgument)
		return
	}

	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}

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

	startPolicy := opent.ActionStartPolicyManual
	if req.Policy != "" {
		startPolicy = req.Policy
	}
	applicant := email
	if req.Author != "" {
		applicant = req.Author
	}

	// create an operation to update component image
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetUndefined,
		Operation: updateNgGlobalComponentImageOperation, // NOTE: MUST match the name in operation definition.
		Reason:    "",
		Applicant: applicant,
		Params: map[opent.ParamName]string{
			opent.ParamCloudConfigClusterType: string(req.ClusterType),
			opent.ParamNgModifyImageComponent: comp,
			opent.ParamCloudConfigVersion:     req.Version,
			opent.ParamNgModifyImageBaseImage: req.BaseImage,
			opent.ParamNgModifyImageTag:       req.Tag,
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        ngoperation.ActionModifyNgImage.Name,
				StartPolicy: startPolicy,
			},
		},
	}
	// related to release ticket if provided
	if req.ReleaseID != "" && req.ChangeID != "" {
		createOPparam.Params[opent.ParamNgModifyImageRelatedChangeLink] = fmt.Sprintf("https://%s/release/%s/change/%s", consts.ConsoleAddr(), req.ReleaseID, req.ChangeID)
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, fmt.Errorf("create operation instance failed: %w", err))
		return
	}

	resp := &ccapi.UpdateDefaultClusterVersionResp{
		InstanceID: *insID,
	}
	ctrl.Success(ctx, resp)
}

// ListAvailableClusterVersions
//
//	@Id				CloudConfigControllerListAvailableClusterVersions
//	@Description	list available cluster versions
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			cluster_type	path		string	true	"cluster_type"
//	@Success		200				{object}	ccapi.ListAvailableClusterVersionsResp
//	@Router			/api/v1/devops/cloudconfig/versions/cluster/{cluster_type} [get]
func (c *CloudConfigController) ListAvailableClusterVersions(ctx *gin.Context) {
	clusterType := ctx.Param("cluster_type")
	if clusterType == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("clusterType is required"), api.ErrCodeInvalidArgument)
		return
	}

	versions, err := c.svc.ListAvailableClusterVersions(ctx, ccent.ClusterType(clusterType))
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.ListAvailableClusterVersionsResp{
		Versions: versions,
	}
	ctrl.Success(ctx, resp)
}

// UpdateDefaultClusterVersion
//
//	@Id				CloudConfigControllerUpdateVersion
//	@Description	update a version
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			cluster_type	path		ccent.ClusterType						true	"cluster_type"
//	@Param			version			path		string									true	"version"
//	@Param			req				body		ccapi.UpdateDefaultClusterVersionReq	true	"request param"
//	@Success		200				{object}	ccapi.UpdateDefaultClusterVersionResp
//	@Router			/api/v1/devops/cloudconfig/versions/cluster/{cluster_type}/{version} [patch]
func (c *CloudConfigController) UpdateDefaultClusterVersion(ctx *gin.Context) {
	clusterType := ctx.Param("cluster_type")
	version := ctx.Param("version")
	if clusterType == "" || version == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("cluster_type and version are required"), api.ErrCodeInvalidArgument)
		return
	}

	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}

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

	// create an operation to update version
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetUndefined,
		Operation: "UpdateGlobalVersion", // NOTE: MUST match the name in operation definition.
		Reason:    "",
		Applicant: email,
		Params: map[opent.ParamName]string{
			opent.ParamCloudConfigClusterType: clusterType,
			opent.ParamCloudConfigVersion:     version,
		},
	}

	if req.IsDefault {
		createOPparam.Actions = append(createOPparam.Actions, opsvcins.CreateInstanceActionParam{
			Name:        opsvcopsaction.ActionUpdateDefaultVersion.Name,
			StartPolicy: opent.ActionStartPolicyImmediate,
		})
	}

	if len(createOPparam.Actions) == 0 {
		ctrl.FailWithCode(ctx, errors.New("no any update"), api.ErrCodeInvalidArgument)
		return
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, fmt.Errorf("create operation instance failed: %w", err))
		return
	}

	resp := &ccapi.UpdateDefaultClusterVersionResp{
		InstanceID: *insID,
	}
	ctrl.Success(ctx, resp)
}

// UpdateDefaultDmVersion
//
//	@Id				CloudConfigControllerUpdateDefaultDmVersion
//	@Description	update default dm version
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			version	path		string									true	"version"
//	@Param			req		body		ccapi.UpdateDefaultClusterVersionReq	true	"request param"
//	@Success		200		{object}	ccapi.UpdateDefaultClusterVersionResp
//	@Router			/api/v1/devops/cloudconfig/defaultversion/dm/{version} [post]
func (c *CloudConfigController) UpdateDefaultDmVersion(ctx *gin.Context) {
	clusterType := ctx.Param("cluster_type")
	version := ctx.Param("version")
	if clusterType == "" || version == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("cluster_type and version are required"), api.ErrCodeInvalidArgument)
		return
	}

	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}

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

	// create an operation to update version
	createOPparam := opsvc.CreateInstanceParam{
		Type: opent.OperationTargetUndefined,
		// todo: update to "UpdateDmVersionRule"
		Operation: "UpdateGlobalVersion", // NOTE: MUST match the name in operation definition.
		Reason:    "",
		Applicant: email,
		Params: map[opent.ParamName]string{
			opent.ParamCloudConfigClusterType: clusterType,
			opent.ParamCloudConfigVersion:     version,
			//	opent.ParamCloudConfigClusterVersion: clusterVersion,
			//	opent.ParamTenantID: tenantID,
		},
	}

	if req.IsDefault {
		createOPparam.Actions = append(createOPparam.Actions, opsvcins.CreateInstanceActionParam{
			Name:        opsvcopsaction.ActionUpdateDefaultVersion.Name,
			StartPolicy: opent.ActionStartPolicyImmediate,
		})
	}

	if len(createOPparam.Actions) == 0 {
		ctrl.FailWithCode(ctx, errors.New("no any update"), api.ErrCodeInvalidArgument)
		return
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, fmt.Errorf("create operation instance failed: %w", err))
		return
	}

	resp := &ccapi.UpdateDefaultClusterVersionResp{
		InstanceID: *insID,
	}
	ctrl.Success(ctx, resp)
}

// ListInstanceTypes
//
//	@Id				CloudConfigControllerListInstanceTypes
//	@Description	list available instance types
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			cluster_type	path		ccent.ClusterType	true	"cluster_type"
//	@Param			version			query		string				false	"version"
//	@Param			config_type		query		string				false	"config_type"
//	@Success		200				{object}	ccapi.ListInstanceTypesResp
//	@Router			/api/v1/devops/cloudconfig/instance-types/{cluster_type} [get]
func (c *CloudConfigController) ListInstanceTypes(ctx *gin.Context) {
	clusterType := ctx.Param("cluster_type")
	if clusterType == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("cluster type are required"), api.ErrCodeInvalidArgument)
		return
	}

	// "" means any
	version := ctx.Query("version")
	configType := ctx.Query("config_type")

	types, err := c.svc.ListAvailableInstanceTypes(ctx, ccent.ClusterType(clusterType), version, configType)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInternal)
		return
	}

	resp := &ccapi.ListInstanceTypesResp{
		InstanceTypes: types,
	}
	ctrl.Success(ctx, resp)
}

func cvtClusterConfigurationLocator(req ccapi.ClusterConfigurationLocator) ccent.ClusterConfigurationLocator {
	return ccent.ClusterConfigurationLocator{
		ClusterType: req.ClusterType,
		ClusterID:   req.ClusterID,
	}
}

func cvtTenantInitConfigurationLocator(req ccapi.TenantInitConfigurationLocator) (ccent.TenantInitConfigurationLocator, error) {
	tenantID, err := strconv.ParseUint(req.TenantID, 10, 64)
	if err != nil {
		return ccent.TenantInitConfigurationLocator{}, err
	}

	return ccent.TenantInitConfigurationLocator{
		TenantID:    tenantID,
		ClusterType: req.ClusterType,
	}, nil
}

func cvtConfigurationTemplateLocator(req ccapi.ConfigurationTemplateLocator) ccent.ConfigurationTemplateLocator {
	return ccent.ConfigurationTemplateLocator{
		ClusterType:  req.ClusterType,
		TemplateType: req.TemplateType,
		TenantID:     req.TenantID,
	}
}
