package cloudconfig

import (
	"encoding/json"
	"errors"
	"fmt"
	"slices"
	"strconv"

	"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/consts"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	ccsvcapi "github.com/tidbcloud/devops/pkg/service/cloudconfig/api"
	opsvc "github.com/tidbcloud/devops/pkg/service/operation"
	opsvcopsaction "github.com/tidbcloud/devops/pkg/service/operation/actions/opsaction"
	opsvcins "github.com/tidbcloud/devops/pkg/service/operation/instance"

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

	"github.com/gin-gonic/gin"
)

// ListDefaultConfigurationChanges
//
//	@Id				CloudConfigControllerListDefaultConfigurationChanges
//	@Description	list template changes
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		query		ccapi.ListDefaultConfigurationChangesReq	false	"request param"
//	@Success		200		{object}	ccapi.ListDefaultConfigurationChangesResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes [get]
func (c *CloudConfigController) ListDefaultConfigurationChanges(ctx *gin.Context) {
	req := &ccapi.ListDefaultConfigurationChangesReq{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := ccsvcapi.ListDefaultConfigurationChangesParam{
		ContainItemType: req.ContainItemType,

		Page:    int(req.Page),
		PerPage: int(req.PerPage),
	}
	changes, total, err := c.svc.ListDefaultConfigurationChanges(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.ListDefaultConfigurationChangesResp{
		Changes: changes,
		Total:   total,
	}
	ctrl.Success(ctx, resp)
}

// GetDefaultConfigurationChange
//
//	@Id				CloudConfigControllerGetDefaultConfigurationChange
//	@Description	get a template change
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string									true	"change id"
//	@Param			req			query		ccapi.GetDefaultConfigurationChangeReq	false	"request param"
//	@Success		200			{object}	ccapi.GetDefaultConfigurationChangeResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id} [get]
func (c *CloudConfigController) GetDefaultConfigurationChange(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	resp := &ccapi.GetDefaultConfigurationChangeResp{}
	change, err := c.svc.GetDefaultConfigurationChange(ctx, changeID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	resp.Change = change

	if req.WithChangeItems {
		changeItems, _, err := c.svc.ListDefaultConfigurationChangeItems(ctx, ccsvcapi.ListDefaultConfigurationChangeItemsParam{
			OwnByChange: &changeID,
			Page:        0,
		})
		if err != nil {
			ctrl.Fail(ctx, err)
			return
		}
		resp.ChangeItems = map[ccent.DefaultConfigurationChangeItemType][]*ccent.DefaultConfigurationChangeItem{}
		for _, changeItem := range changeItems {
			if _, ok := resp.ChangeItems[changeItem.ItemType]; !ok {
				resp.ChangeItems[changeItem.ItemType] = []*ccent.DefaultConfigurationChangeItem{}
			}
			resp.ChangeItems[changeItem.ItemType] = append(resp.ChangeItems[changeItem.ItemType], changeItem)
		}
	}

	ctrl.Success(ctx, resp)
}

// UpdateTemplateDefaultCaseBindingByOp
//
//	@Id				CloudConfigControllerUpdateTemplateDefaultCaseBindingByOp
//	@Description	create a template change
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		body		ccapi.UpdateTemplateDefaultCaseBindingByOpReq	true	"request param"
//	@Success		200		{object}	ccapi.UpdateTemplateDefaultCaseBindingResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/template-bindings/update-by-op [post]
func (c *CloudConfigController) UpdateTemplateDefaultCaseBindingByOp(ctx *gin.Context) {
	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}

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

	// create operation
	opReqData, err := json.Marshal(req.DefaultCaseBinding)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("marshal failed: %s", err), api.ErrCodeInternal)
		return
	}

	createOPParam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "UpdateTenantDefaultTemplateBinding", // NOTE: MUST match the name in operation definition.
		Reason:    req.Reason,
		Applicant: email,
		Params: map[opent.ParamName]string{
			opent.ParamCloudConfigUpdateDefaultTemplateBindingRequest: string(opReqData),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvcopsaction.ActionUpdateDefaultTemplateBinding.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	// inject tenant info if provided
	if req.DefaultCaseBinding.DefaultCase.TenantID != "" {
		tenantID, err := strconv.ParseUint(req.DefaultCaseBinding.DefaultCase.TenantID, 10, 64)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("invalid tenant id: %w", err), api.ErrCodeInvalidArgument)
			return
		}

		tenant, err := c.tenantSvc.GetTenant(ctx, tenantID)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("get tenant failed: %w", err), api.ErrCodeInternal)
			return
		}
		createOPParam.Params[opent.ParamTenantID] = strconv.FormatUint(tenant.Id, 10)
		createOPParam.Params[opent.ParamTenantRegion] = string(tenant.GetTenantRegion())
	} else {
		ctrl.FailWithCode(ctx, errors.New("tenant id is required"), 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.UpdateTemplateDefaultCaseBindingResp{
		InstanceID: *insID,
	}
	ctrl.Success(ctx, resp)
}

// DeleteTemplateDefaultCaseBindingByOp
//
//	@Id				CloudConfigControllerDeleteTemplateDefaultCaseBindingByOp
//	@Description	delete a template change
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		body		ccapi.DeleteTemplateDefaultCaseBindingByOpReq	true	"request param"
//	@Success		200		{object}	ccapi.DeleteTemplateDefaultCaseBindingResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/template-bindings/delete-by-op [post]
func (c *CloudConfigController) DeleteTemplateDefaultCaseBindingByOp(ctx *gin.Context) {
	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}

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

	// create operation
	opReqData, err := json.Marshal(req.DefaultCaseBinding)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("marshal failed: %s", err), api.ErrCodeInternal)
		return
	}

	createOPParam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetTenant,
		Operation: "DeleteTenantDefaultTemplateBinding", // NOTE: MUST match the name in operation definition.
		Reason:    req.Reason,
		Applicant: email,
		Params: map[opent.ParamName]string{
			opent.ParamCloudConfigDeleteDefaultTemplateBindingRequest: string(opReqData),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvcopsaction.ActionDeleteDefaultTemplateBinding.Name,
				StartPolicy: opent.ActionStartPolicyImmediate,
			},
		},
	}

	// inject tenant info if provided
	if req.DefaultCaseBinding.DefaultCase.TenantID != "" {
		tenantID, err := strconv.ParseUint(req.DefaultCaseBinding.DefaultCase.TenantID, 10, 64)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("invalid tenant id: %w", err), api.ErrCodeInvalidArgument)
			return
		}

		tenant, err := c.tenantSvc.GetTenant(ctx, tenantID)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("get tenant failed: %w", err), api.ErrCodeInternal)
			return
		}
		createOPParam.Params[opent.ParamTenantID] = strconv.FormatUint(tenant.Id, 10)
		createOPParam.Params[opent.ParamTenantRegion] = string(tenant.GetTenantRegion())
	} else {
		ctrl.FailWithCode(ctx, errors.New("tenant id is required"), 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.DeleteTemplateDefaultCaseBindingResp{
		InstanceID: *insID,
	}
	ctrl.Success(ctx, resp)
}

// ChangeTemplateDefaultCaseBindings
//
//	@Id				CloudConfigControllerChangeTemplateDefaultCaseBindings
//	@Description	create a template change
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		body		ccapi.ChangeTemplateDefaultCaseBindingsReq	true	"request param"
//	@Success		200		{object}	ccapi.CreateDefaultConfigurationChangeResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/template-bindings/change [post]
func (c *CloudConfigController) ChangeTemplateDefaultCaseBindings(ctx *gin.Context) {
	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}

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

	param := ccsvcapi.CreateDefaultConfigurationChangeParam{
		Applicant:                        email,
		Reason:                           req.Reason,
		DisplayName:                      "Modify Default Templates",
		TemplateDefaultCaseBindingChange: req.DefaultCaseBindings,
	}
	id, err := c.svc.CreateDefaultConfigurationChange(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.CreateDefaultConfigurationChangeResp{ChangeID: id}
	ctrl.Success(ctx, resp)
}

// BatchUpdateDefaultConfigurationChangeItems
//
//	@Id				CloudConfigControllerBatchUpdateDefaultConfigurationChangeItems
//	@Description	batch update change items
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string												true	"change id"
//	@Param			req			body		ccapi.BatchUpdateDefaultConfigurationChangeItemsReq	true	"request param"
//	@Success		200			{object}	api.BaseResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/items/batchUpdate [post]
func (c *CloudConfigController) BatchUpdateDefaultConfigurationChangeItems(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	param := ccsvcapi.BatchUpdateDefaultConfigurationChangeItemsParam{}
	if req.BatchUpdateTemplate != nil {
		param.BatchUpdateTemplate = &ccsvcapi.BatchUpdateTemplateParam{
			Items:         req.BatchUpdateTemplate.Items,
			ParamsToSet:   req.BatchUpdateTemplate.ParamsToSet,
			ParamsToUnset: req.BatchUpdateTemplate.ParamsToUnset,
		}
	}
	if req.BatchUpdateComponentConfigs != nil {
		param.UpdateDefaultComponents = req.BatchUpdateComponentConfigs.Components
	}

	err = c.svc.BatchUpdateDefaultConfigurationChangeItems(ctx, changeID, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// ListDefaultConfigurationChangeItems
//
//	@Id				CloudConfigControllerListDefaultConfigurationChangeItems
//	@Description	list change items
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string											true	"change id"
//	@Param			request		query		ccapi.ListDefaultConfigurationChangeItemsReq	false	"request"
//	@Success		200			{object}	ccapi.ListDefaultConfigurationChangeItemsResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/items [get]
func (c *CloudConfigController) ListDefaultConfigurationChangeItems(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	param := ccsvcapi.ListDefaultConfigurationChangeItemsParam{
		OwnByChange: &changeID,
		TypeIs:      req.TypeIs,
		DataIs:      req.DataIs,
		Page:        0, // all items
	}
	items, total, err := c.svc.ListDefaultConfigurationChangeItems(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.ListDefaultConfigurationChangeItemsResp{
		Items: items,
		Total: total,
	}
	ctrl.Success(ctx, resp)
}

// ListUnreleasedDefaultConfigurationChangeItems
//
//	@Id				CloudConfigControllerListUnreleasedDefaultConfigurationChangeItems
//	@Description	list unreleased change items
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string													true	"change id"
//	@Param			request		query		ccapi.ListUnreleasedDefaultConfigurationChangeItemsReq	false	"request"
//	@Success		200			{object}	ccapi.ListUnreleasedDefaultConfigurationChangeItemsResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/items/unreleased [get]
func (c *CloudConfigController) ListUnreleasedDefaultConfigurationChangeItems(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	req := &ccapi.ListUnreleasedDefaultConfigurationChangeItemsReq{}
	err = ctx.ShouldBindQuery(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.EnvironmentIs == "" {
		ctrl.FailWithCode(ctx, errors.New("environment_is is required"), api.ErrCodeInvalidArgument)
		return
	}

	param := ccsvcapi.ListDefaultConfigurationChangeItemsParam{
		OwnByChange: &changeID,
		TypeIs:      req.TypeIs,
		Page:        0, // all items
	}
	items, _, err := c.svc.ListDefaultConfigurationChangeItems(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	if len(items) == 0 {
		ctrl.FailWithCode(ctx, errors.New("no items"), api.ErrCodeNotFound)
		return
	}

	itemReleases, err := c.svc.ListDefaultConfigurationChangeItemReleases(ctx, ccsvcapi.ListDefaultConfigurationChangeItemReleasesParam{
		LatestPerItem: true,
		EnvironmentIs: &req.EnvironmentIs,
		ItemIDIn: container.TransformSlice(items, func(e *ccent.DefaultConfigurationChangeItem) uint64 {
			return e.ID
		}),
	})
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	unreleasedItems := []*ccent.DefaultConfigurationChangeItem{}
	for _, item := range items {
		var itemReleaseData *ccent.DefaultConfigurationChangeItemReleaseDataUnion
		idx := slices.IndexFunc(itemReleases, func(release *ccent.DefaultConfigurationChangeItemRelease) bool {
			return release.ItemID == item.ID
		})
		if idx != -1 {
			itemReleaseData = itemReleases[idx].Data
		}

		if !item.Data.Releasable() || item.Data.ToRelease().IsReleased(itemReleaseData) {
			continue
		}

		unreleasedItems = append(unreleasedItems, item)
	}

	resp := &ccapi.ListUnreleasedDefaultConfigurationChangeItemsResp{
		UnreleasedItems: unreleasedItems,
	}
	ctrl.Success(ctx, resp)
}

// StatDefaultConfigurationChangeItems
//
//	@Id				CloudConfigControllerStatDefaultConfigurationChangeItems
//	@Description	stat change items
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string											true	"change id"
//	@Param			req			query		ccapi.StatDefaultConfigurationChangeItemsReq	true	"request param"
//	@Success		200			{object}	ccapi.StatDefaultConfigurationChangeItemsResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/items/stat [get]
func (c *CloudConfigController) StatDefaultConfigurationChangeItems(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	param := ccsvcapi.StatDefaultConfigurationChangeItemsParam{
		OwnByChange:                             &changeID,
		ConfigTypesOfTemplateDefaultCaseBinding: req.ConfigTypesOfTemplateDefaultCaseBinding,
	}

	result, err := c.svc.StatDefaultConfigurationChangeItems(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.StatDefaultConfigurationChangeItemsResp{
		ConfigTypesOfTemplateDefaultCaseBinding: result.ConfigTypesOfTemplateDefaultCaseBinding,
	}
	ctrl.Success(ctx, resp)
}

// CreateDefaultConfigurationChangeRelease
//
//	@Id				CloudConfigControllerCreateDefaultConfigurationChangeRelease
//	@Description	release a change to a environment
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string												true	"change id"
//	@Param			req			body		ccapi.CreateDefaultConfigurationChangeReleaseReq	false	"request param"
//	@Success		200			{object}	api.BaseResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/releases [post]
func (c *CloudConfigController) CreateDefaultConfigurationChangeRelease(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	err = c.svc.CreateDefaultConfigurationChangeRelease(ctx, changeID, req.Environment, req.Description)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// RollbackDefaultConfigurationChangeRelease
//
//	@Id				CloudConfigControllerRollbackDefaultConfigurationChangeRelease
//	@Description	rollback a change to a environment
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string												true	"change id"
//	@Param			req			body		ccapi.RollbackDefaultConfigurationChangeReleaseReq	false	"request param"
//	@Success		200			{object}	api.BaseResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/releases/rollback [post]
func (c *CloudConfigController) RollbackDefaultConfigurationChangeRelease(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	err = c.svc.RollbackDefaultConfigurationChangeRelease(ctx, changeID, req.Environment, req.ReleaseNumber, req.Description)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// ListDefaultConfigurationChangeReleases
//
//	@Id				CloudConfigControllerListDefaultConfigurationChangeReleases
//	@Description	list change releases
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string											true	"change id"
//	@Param			request		query		ccapi.ListDefaultConfigurationChangeReleasesReq	false	"request"
//	@Success		200			{object}	ccapi.ListDefaultConfigurationChangeReleasesResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/releases [get]
func (c *CloudConfigController) ListDefaultConfigurationChangeReleases(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	param := ccsvcapi.ListDefaultConfigurationChangeReleasesParam{
		OwnByChange:   &changeID,
		EnvironmentIs: req.EnvironmentIs,
	}
	releases, err := c.svc.ListDefaultConfigurationChangeReleases(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.ListDefaultConfigurationChangeReleasesResp{
		Releases: releases,
	}
	ctrl.Success(ctx, resp)
}

// GetDefaultConfigurationChangeRelease
//
//	@Id				CloudConfigControllerGetDefaultConfigurationChangeRelease
//	@Description	list change releases
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string											true	"change id"
//	@Param			release_id	path		string											true	"release id"
//	@Param			request		query		ccapi.GetDefaultConfigurationChangeReleaseReq	false	"request"
//	@Success		200			{object}	ccapi.GetDefaultConfigurationChangeRelease
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/releases/{release_id} [get]
func (c *CloudConfigController) GetDefaultConfigurationChangeRelease(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	releaseID, err := strconv.ParseUint(ctx.Param("release_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	release, err := c.svc.GetDefaultConfigurationChangeReleases(ctx, releaseID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.GetDefaultConfigurationChangeRelease{
		Releases: release,
	}

	if req.WithReleasedItems {
		itemReleases, err := c.svc.ListDefaultConfigurationChangeItemReleases(ctx, ccsvcapi.ListDefaultConfigurationChangeItemReleasesParam{
			OwnByChange:     &changeID,
			EnvironmentIs:   &release.Environment,
			ReleaseNumberIs: &release.ReleaseNumber,
		})
		if err != nil {
			ctrl.Fail(ctx, err)
			return
		}

		items, _, err := c.svc.ListDefaultConfigurationChangeItems(ctx, ccsvcapi.ListDefaultConfigurationChangeItemsParam{
			IDIn: container.TransformSlice(itemReleases, func(e *ccent.DefaultConfigurationChangeItemRelease) uint64 {
				return e.ItemID
			}),
			Page: 0, // no pagination
		})
		if err != nil {
			ctrl.Fail(ctx, err)
			return
		}

		for _, itemRelease := range itemReleases {
			idx := slices.IndexFunc(items, func(e *ccent.DefaultConfigurationChangeItem) bool {
				return e.ID == itemRelease.ItemID
			})
			if idx == -1 {
				ctrl.Fail(ctx, fmt.Errorf("found a item release '%d' without item", itemRelease.ID))
				return
			}

			items[idx].Data = itemRelease.Data.ToItemData(items[idx].Data)
			resp.ReleasedItems = append(resp.ReleasedItems, items[idx])
		}

	}

	ctrl.Success(ctx, resp)
}

// ListDefaultConfigurationChangeItemRelease
//
//	@Id				CloudConfigControllerListDefaultConfigurationChangeItemRelease
//	@Description	list change item releases
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string												true	"change id"
//	@Param			release_id	path		string												true	"release id"
//	@Param			request		query		ccapi.ListDefaultConfigurationChangeItemReleaseReq	false	"request"
//	@Success		200			{object}	ccapi.ListDefaultConfigurationChangeItemReleaseResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/releases/{release_id}/items [get]
func (c *CloudConfigController) ListDefaultConfigurationChangeItemRelease(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	releaseID, err := strconv.ParseUint(ctx.Param("release_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	release, err := c.svc.GetDefaultConfigurationChangeReleases(ctx, releaseID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	itemReleases, err := c.svc.ListDefaultConfigurationChangeItemReleases(ctx, ccsvcapi.ListDefaultConfigurationChangeItemReleasesParam{
		OwnByChange:     &changeID,
		EnvironmentIs:   &release.Environment,
		ReleaseNumberIs: &release.ReleaseNumber,
	})
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.ListDefaultConfigurationChangeItemReleaseResp{
		Items: itemReleases,
	}
	ctrl.Success(ctx, resp)
}

// CancelDefaultConfigurationChangeRelease
//
//	@Id				CloudConfigControllerCancelDefaultConfigurationChangeRelease
//	@Description	cancel a release
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string	true	"change id"
//	@Param			release_id	path		string	true	"release id"
//	@Success		200			{object}	api.BaseResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/releases/{release_id}/cancel [post]
func (c *CloudConfigController) CancelDefaultConfigurationChangeRelease(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	releaseID, err := strconv.ParseUint(ctx.Param("release_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	err = c.svc.CancelDefaultConfigurationChangeRelease(ctx, changeID, releaseID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// CloseDefaultConfigurationChange
//
//	@Id				CloudConfigControllerCloseDefaultConfigurationChange
//	@Description	close a template change
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			change_id	path		string										true	"change id"
//	@Param			req			body		ccapi.CloseDefaultConfigurationChangeReq	false	"request param"
//	@Success		200			{object}	api.BaseResp
//	@Failure		default		{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/changes/{change_id}/close [post]
func (c *CloudConfigController) CloseDefaultConfigurationChange(ctx *gin.Context) {
	changeID, err := strconv.ParseUint(ctx.Param("change_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	err = c.svc.CloseDefaultConfigurationChange(ctx, changeID, req.Result, req.Reason)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// UpdateDefaultComponents
//
//	@Id				CloudConfigControllerUpdateDefaultComponents
//	@Description	update default components
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		query		ccapi.UpdateDefaultComponentsReq	true	"request param"
//	@Success		200		{object}	api.BaseResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/components [patch]
func (c *CloudConfigController) UpdateDefaultComponents(ctx *gin.Context) {
	req := &ccapi.UpdateDefaultComponentsReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	err = c.svc.UpdateDefaultComponents(ctx, req.Components)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// ChangeDefaultComponents
//
//	@Id				CloudConfigControllerChangeDefaultComponents
//	@Description	create default components
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		body		ccapi.ChangeDefaultComponentsReq	true	"request param"
//	@Success		200		{object}	ccapi.CreateDefaultConfigurationChangeResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/components/change [post]
func (c *CloudConfigController) ChangeDefaultComponents(ctx *gin.Context) {
	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}

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

	param := ccsvcapi.CreateDefaultConfigurationChangeParam{
		Applicant:                    email,
		Reason:                       req.Reason,
		DisplayName:                  "Modify Default Component Configurations",
		DefaultComponentConfigChange: req.Components,
	}
	id, err := c.svc.CreateDefaultConfigurationChange(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.CreateDefaultConfigurationChangeResp{ChangeID: id}
	ctrl.Success(ctx, resp)
}

// ListDefaultVersionRules
//
//	@Id				CloudConfigControllerListDefaultVersionRules
//	@Description	List rules for choose default version
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		query		ccapi.ListDefaultVersionRulesReq	true	"request param"
//	@Success		200		{object}	ccapi.ListDefaultVersionRulesResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/versions/defaultrules [get]
func (c *CloudConfigController) ListDefaultVersionRules(ctx *gin.Context) {
	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}

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

	clusterType, err := parseListDefaultVersionRulesClusterType(req.ClusterType)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	component, exist := ctx.GetQuery("component")
	var componentFilter *string
	if exist {
		componentFilter = &component
	}

	resp, err := c.cfgCenter.ConfigAPI.ListDefaultVersionRules(ctx, &ccproto.ListDefaultVersionRulesRequest{
		ClusterType: clusterType,
		Component:   componentFilter,
		TenantId:    pointer.Pointer(uint64(0)), // not show tenant level rules
	})
	if err != nil {
		ctrl.Fail(ctx, fmt.Errorf("get DefaultVersionRules failed: %w", err))
		return
	}

	ctrl.Success(ctx, buildListDefaultVersionRulesResp(resp))
}

func parseListDefaultVersionRulesClusterType(clusterType ccent.ClusterType) (ccproto.ClusterType, error) {
	value, ok := ccproto.ClusterType_value[string(clusterType)]
	if !ok || value == int32(ccproto.ClusterType_CLUSTER_TYPE_UNSPECIFIED) {
		return ccproto.ClusterType_CLUSTER_TYPE_UNSPECIFIED, fmt.Errorf("invalid cluster_type: %s", clusterType)
	}
	return ccproto.ClusterType(value), nil
}

func buildListDefaultVersionRulesResp(resp *ccproto.ListDefaultVersionRulesResponse) *ccapi.ListDefaultVersionRulesResp {
	result := &ccapi.ListDefaultVersionRulesResp{
		Rules: []*ccapi.DefaultVersionRule{},
	}
	if resp == nil {
		return result
	}

	result.Rules = make([]*ccapi.DefaultVersionRule, 0, len(resp.GetRules()))
	for _, rule := range resp.GetRules() {
		if rule == nil {
			continue
		}
		result.Rules = append(result.Rules, &ccapi.DefaultVersionRule{
			ClusterType:    defaultVersionRuleClusterType(rule.GetClusterType()),
			Component:      rule.GetComponent(),
			ClusterVersion: rule.GetClusterVersion(),
			CloudProvider:  rule.GetCloudProvider(),
			Region:         rule.GetRegion(),
			Version:        rule.GetVersion(),
		})
	}
	return result
}

func defaultVersionRuleClusterType(clusterType ccproto.ClusterType) ccent.ClusterType {
	if clusterType == ccproto.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
		return ccent.ClusterTypeUnknown
	}
	value, ok := ccproto.ClusterType_name[int32(clusterType)]
	if !ok {
		return ccent.ClusterTypeUnknown
	}
	return ccent.ClusterType(value)
}

// ReleaseVersion
//
//	@Id				CloudConfigControllerReleaseVersion
//	@Description	release a version
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		body		ccapi.ReleaseVersionReq	true	"request param"
//	@Success		200		{object}	ccapi.CreateDefaultConfigurationChangeResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/versions/release [post]
func (c *CloudConfigController) ReleaseVersion(ctx *gin.Context) {
	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}

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

	param := ccsvcapi.CreateDefaultConfigurationChangeParam{
		Applicant:   email,
		Reason:      fmt.Sprintf("release version for %s", req.TargetVersion),
		DisplayName: fmt.Sprintf("Release Version %s", req.TargetVersion),
		VersionRelease: &ccsvcapi.ReleaseVersionParam{
			ClusterType:   req.ClusterType,
			SourceVersion: req.SourceVersion,
			TargetVersion: req.TargetVersion,
		},
	}
	id, err := c.svc.CreateDefaultConfigurationChange(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.CreateDefaultConfigurationChangeResp{ChangeID: id}
	ctrl.Success(ctx, resp)
}

// ImportVersion
//
//	@Id				CloudConfigControllerImportVersions
//	@Description	Import versions from gitops
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		body		ccapi.ImportVersionsReq	true	"request param"
//	@Success		200		{object}	ccapi.ImportVersionsResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/default-config/versions/import [post]
func (c *CloudConfigController) ImportVersion(ctx *gin.Context) {
	email := ctx.GetString(consts.CtxUserEmail)
	if email == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("account not found in ctx"), api.ErrCodeInternal)
		return
	}

	req := &ccapi.ImportVersionsReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.GitopsBranch != "3layer-prod" && consts.GetEnv() == consts.EnvironmentProd {
		ctrl.FailWithCode(ctx, fmt.Errorf("only 3layer-prod branch is allowed in prod env"), api.ErrCodeInvalidArgument)
		return
	}
	// err = c.svc.SyncVersionsFromGitops(ctx, req.MinVersion, req.MaxVersion, req.GitopsBranch, req.TierBranch)

	// create an operation to update version
	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetUndefined,
		Operation: "SyncGitopsVersions", // NOTE: MUST match the name in operation definition.
		Reason:    req.Reason,
		Applicant: email,
		Params: opsvcopsaction.SyncGitopsVersionsParam{
			ClusterType:  ccent.ClusterTypeDedicatedTiDB,
			GitopsBranch: req.GitopsBranch,
			TierBranch:   req.TierBranch,
			MinVersion:   req.MinVersion,
			MaxVersion:   req.MaxVersion,
		}.ToMap(),
	}

	createOPparam.Actions = append(createOPparam.Actions, opsvcins.CreateInstanceActionParam{
		Name:        opsvcopsaction.ActionSyncGitopsVersions.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.ImportVersionsResp{
		InstanceID: *insID,
	}
	ctrl.Success(ctx, resp)
}
