package cloudconfig

import (
	"fmt"
	"strconv"

	"github.com/gin-gonic/gin"
	ccproto "github.com/tidbcloud/config-center/proto/config_center"
	"github.com/tidbcloud/devops/api"
	ccapi "github.com/tidbcloud/devops/api/cloudconfig"
	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	ccsvcapi "github.com/tidbcloud/devops/pkg/service/cloudconfig/api"
)

// GetClusterConfiguration
//
//	@Id				CloudConfigControllerGetClusterConfiguration
//	@Description	get the current configuration of a cluster
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		query		ccapi.ClusterConfigurationLocator	true	"request param"
//	@Success		200		{object}	ccapi.GetConfigurationResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/cluster/config [get]
func (c *CloudConfigController) checkClusterLocatorScope(ctx *gin.Context, clusterIDStr string) error {
	clusterID, err := strconv.ParseUint(clusterIDStr, 10, 64)
	if err != nil {
		// Fail closed for tenant-restricted callers: if we cannot parse the
		// cluster id, we cannot verify it falls within the allowed tenants, so
		// reject instead of silently letting the request through.
		if ctrl.IsTenantRestricted(ctx) {
			return fmt.Errorf("invalid cluster_id %q: %w", clusterIDStr, err)
		}
		return nil
	}
	return ctrl.CheckClusterScope(ctx, c.coreSvcCli, clusterID)
}

func (c *CloudConfigController) GetClusterConfiguration(ctx *gin.Context) {
	req := &ccapi.ClusterConfigurationLocator{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	if err := c.checkClusterLocatorScope(ctx, req.ClusterID); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	loc := cvtClusterConfigurationLocator(*req)

	reason, err := c.svc.ClusterConfigurationChangeable(ctx, loc)
	if err != nil {
		ctrl.Fail(ctx, err)
	}

	configuration, err := c.svc.GetClusterConfiguration(ctx, loc, ccproto.DEDICATED_TIDB_COMPONENT_TIDB)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	// FIXME: workaround to update dm version before create
	if _, ok := configuration.Components[ccproto.DEDICATED_TIDB_COMPONENT_DM]; !ok && loc.ClusterType == ccent.ClusterTypeDedicatedTiDB {
		configuration.Components[ccproto.DEDICATED_TIDB_COMPONENT_DM] = &ccent.Component{
			Name:              ccproto.DEDICATED_TIDB_COMPONENT_DM,
			BaseImage:         "UNSPECIFIED",
			EffectiveImageTag: "UNSPECIFIED",
		}
	}

	resp := &ccapi.GetConfigurationResp{
		Configuration: configuration,
		Changeable: &ccapi.ConfigurationChangeable{
			Changeable: reason == "",
			Reason:     reason,
		},
	}
	ctrl.Success(ctx, resp)
}

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

	if err := c.checkClusterLocatorScope(ctx, req.ClusterID); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	loc := cvtClusterConfigurationLocator(req.ClusterConfigurationLocator)
	param := ccsvcapi.ListConfigurationChangesParam{
		TargetCluster:             &loc,
		IDOnly:                    req.IDOnly,
		HaveModifiedParameterLike: req.HaveModifiedParameterLike,
		CreatedAtStartUnixSec:     req.CreatedAtStartUnixSec,
		CreatedAtEndUnixSec:       req.CreatedAtEndUnixSec,

		Page:    int(req.Page),
		PerPage: int(req.PerPage),
	}

	changes, total, err := c.svc.ListConfigurationChanges(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

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

// CreateClusterConfigurationChange
//
//	@Id				CloudConfigControllerCreateClusterConfigurationChange
//	@Description	change the configuration of a cluster
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		body		ccapi.CreateClusterConfigurationChangeReq	true	"request param"
//	@Success		200		{object}	ccapi.CreateConfigurationChangeResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/cluster/changes [post]
func (c *CloudConfigController) CreateClusterConfigurationChange(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.CreateClusterConfigurationChangeReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	if err := c.checkClusterLocatorScope(ctx, req.ClusterID); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	loc := cvtClusterConfigurationLocator(req.ClusterConfigurationLocator)
	param := BuildParamToCreateConfigurationChange(&req.CreateConfigurationChangeReq, email)
	param.TargetCluster = &loc

	id, err := c.svc.CreateConfigurationChange(ctx, *param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

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

// PreviewClusterConfigurationChange
//
//	@Id				CloudConfigControllerPreviewClusterConfigurationChange
//	@Description	change the configuration of a cluster
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req		body		ccapi.PreviewClusterConfigurationChangeReq	true	"request param"
//	@Success		200		{object}	ccapi.PreviewConfigurationChangeResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/cloudconfig/cluster/changes/preview [post]
func (c *CloudConfigController) PreviewClusterConfigurationChange(ctx *gin.Context) {
	req := &ccapi.PreviewClusterConfigurationChangeReq{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	if err := c.checkClusterLocatorScope(ctx, req.ClusterID); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return
	}

	loc := cvtClusterConfigurationLocator(req.ClusterConfigurationLocator)
	param := BuildParamToPreviewConfigurationChange(&req.PreviewConfigurationChangeReq)
	param.TargetCluster = &loc
	result, err := c.svc.PreviewConfigurationChange(ctx, *param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &ccapi.PreviewConfigurationChangeResp{
		Change:        result.Change,
		Configuration: result.Configuration,
		ApplyPolicies: result.ApplyPolicies,
		Reason:        result.Reason,
	}
	ctrl.Success(ctx, resp)
}
