package cloudconfig

import (
	"fmt"
	"strconv"

	"github.com/tidbcloud/devops/api"
	ccapi "github.com/tidbcloud/devops/api/cloudconfig"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	ccsvcapi "github.com/tidbcloud/devops/pkg/service/cloudconfig/api"

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

// GetTenantInitConfiguration
//
//	@Id				CloudConfigControllerGetTenantInitConfiguration
//	@Description	get a tenant init config
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req	query		ccapi.TenantInitConfigurationLocator	false	"request param"
//	@Success		200	{object}	ccapi.GetConfigurationResp
//	@Router			/api/v1/devops/cloudconfig/tenantinit/config [get]
func (c *CloudConfigController) checkTenantLocatorScope(ctx *gin.Context, tenantIDStr string) error {
	tenantID, err := strconv.ParseUint(tenantIDStr, 10, 64)
	if err != nil {
		// Fail closed for tenant-restricted callers: without a parseable
		// tenant id we cannot verify scope, so reject rather than skip.
		if ctrl.IsTenantRestricted(ctx) {
			return fmt.Errorf("invalid tenant_id %q: %w", tenantIDStr, err)
		}
		return nil
	}
	return ctrl.CheckTenantScope(ctx, tenantID)
}

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

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

	loc, err := cvtTenantInitConfigurationLocator(*req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

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

	defautlVersions, err := c.svc.GetTenantInitVersions(ctx, loc)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

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

// ListTenantInitImages
//
//	@Id				CloudConfigControllerGetTenantInitImages
//	@Description	get a tenant init images
//	@Tags			CloudConfigController
//	@Accept			json
//	@Produce		json
//	@Param			req	query		ccapi.TenantInitConfigurationLocator	false	"request param"
//	@Success		200	{object}	ccapi.TenantImages
//	@Router			/api/v1/devops/cloudconfig/tenantinit/images [get]
func (c *CloudConfigController) ListTenantInitImages(ctx *gin.Context) {
	req := &ccapi.TenantInitConfigurationLocator{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

	loc, err := cvtTenantInitConfigurationLocator(*req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp, err := c.svc.ListTenantInitImages(ctx, loc)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	ctrl.Success(ctx, resp)
}

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

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

	loc, err := cvtTenantInitConfigurationLocator(req.TenantInitConfigurationLocator)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := ccsvcapi.ListConfigurationChangesParam{
		TargetTenantInit:          &loc,
		IDOnly:                    req.IDOnly,
		HaveModifiedParameterLike: req.HaveModifiedParameterLike,

		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)
}

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

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

	loc, err := cvtTenantInitConfigurationLocator(req.TenantInitConfigurationLocator)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := BuildParamToPreviewConfigurationChange(&req.PreviewConfigurationChangeReq)
	param.TargetTenantInit = &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)
}

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

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

	loc, err := cvtTenantInitConfigurationLocator(req.TenantInitConfigurationLocator)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := BuildParamToCreateConfigurationChange(&req.CreateConfigurationChangeReq, email)
	param.TargetTenantInit = &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)
}
