package cluster

import (
	"fmt"
	"strconv"

	"github.com/gin-gonic/gin"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"google.golang.org/protobuf/encoding/protojson"

	"github.com/tidbcloud/devops/api"
	clusterapi "github.com/tidbcloud/devops/api/cluster"
	opent "github.com/tidbcloud/devops/entity/operation"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	opsvcapi "github.com/tidbcloud/devops/pkg/service/operation"
	opsvcclusteraction "github.com/tidbcloud/devops/pkg/service/operation/actions/clusteraction"
	opsvcins "github.com/tidbcloud/devops/pkg/service/operation/instance"
)

// ApplyHeterogeneousDeploy
//
//	@Id				ClusterServiceApplyHeterogeneousDeploy
//	@Description	apply heterogeneous deploy for a cluster
//	@Tags			ClusterService
//	@Accept			json
//	@Produce		json
//	@Param			cluster_id	path		string										true	"cluster id"
//	@Param			req			body		clusterapi.ApplyHeterogeneousDeployRequest	true	"request param"
//	@Success		200			{object}	clusterapi.ApplyHeterogeneousDeployResponse
//	@Router			/api/v1/devops/clusters/{cluster_id}/hetero-deploy/apply [post]
func (c *ClusterController) ApplyHeterogeneousDeploy(ctx *gin.Context) {
	clusterID, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("cluster_id is not a int"), api.ErrCodeInvalidArgument)
		return
	}
	cluster, err := c.coreSvcCli.Mgmt.GetCluster(ctx, clusterID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

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

	req := &clusterapi.ApplyHeterogeneousDeployRequest{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	componentType, err := clusterapi.DedicatedComponentTypeFromAPI(req.ComponentType)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("invalid component_type: %s", req.ComponentType), api.ErrCodeInvalidArgument)
		return
	}
	var tidbGroupID uint64
	if req.TiDBGroupID != "" {
		tidbGroupID, err = strconv.ParseUint(req.TiDBGroupID, 10, 64)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("invalid tidb_group_id: %s", req.TiDBGroupID), api.ErrCodeInvalidArgument)
			return
		}
	}

	opReq := &mgmtv1.ApplyGKEHeterogeneousDeployRequest{
		TenantId:                cluster.GetOrganizationId(),
		ProjectId:               cluster.GetProjectId(),
		ClusterId:               clusterID,
		ComponentType:           componentType,
		CurrentInstanceType:     req.CurrentInstanceType,
		AlternativeInstanceType: req.AlternativeInstanceType,
		TidbGroupId:             tidbGroupID,
	}
	opReqData, err := protojson.Marshal(opReq)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("marshal proto message failed: %s", err), api.ErrCodeInternal)
		return
	}

	insID, err := c.operationSvc.CreateInstance(ctx, opsvcapi.CreateInstanceParam{
		Type:      opent.OperationTargetCluster,
		Operation: "ApplyHeterogeneousDeployForDedicatedCluster",
		Reason:    req.Reason,
		Applicant: email,
		Params: map[opent.ParamName]string{
			opent.ParamClusterID:                       strconv.FormatUint(clusterID, 10),
			opent.ParamApplyHeterogeneousDeployRequest: string(opReqData),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvcclusteraction.ActionApplyHeterogeneousDeploy.Name,
				StartPolicy: opent.ActionStartPolicyManual,
			},
		},
	})
	if err != nil {
		ctrl.Fail(ctx, fmt.Errorf("create operation instance failed: %w", err))
		return
	}

	ctrl.Success(ctx, &clusterapi.ApplyHeterogeneousDeployResponse{InstanceID: *insID})
}

// GetHeterogeneousDeploy
//
//	@Id				ClusterServiceGetHeterogeneousDeploy
//	@Description	get heterogeneous deploy for a cluster
//	@Tags			ClusterService
//	@Accept			json
//	@Produce		json
//	@Param			cluster_id	path		string										true	"cluster id"
//	@Param			req			query		clusterapi.GetHeterogeneousDeployRequest	true	"request param"
//	@Success		200			{object}	clusterapi.GetHeterogeneousDeployResponse
//	@Router			/api/v1/devops/clusters/{cluster_id}/hetero-deploy/query [get]
func (c *ClusterController) GetHeterogeneousDeploy(ctx *gin.Context) {
	clusterID, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("cluster_id is not a int"), api.ErrCodeInvalidArgument)
		return
	}
	cluster, err := c.coreSvcCli.Mgmt.GetCluster(ctx, clusterID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	req := &clusterapi.GetHeterogeneousDeployRequest{}
	if err := ctx.ShouldBindQuery(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	componentType, err := clusterapi.DedicatedComponentTypeFromAPI(req.ComponentType)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("invalid component_type: %s", req.ComponentType), api.ErrCodeInvalidArgument)
		return
	}
	var tidbGroupID uint64
	if req.TiDBGroupID != "" {
		tidbGroupID, err = strconv.ParseUint(req.TiDBGroupID, 10, 64)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("invalid tidb_group_id: %s", req.TiDBGroupID), api.ErrCodeInvalidArgument)
			return
		}
	}

	deploy, err := c.coreSvcCli.Mgmt.Cluster.GetGKEHeterogeneousDeploy(ctx, &mgmtv1.GetGKEHeterogeneousDeployRequest{
		ClusterId:     clusterID,
		ComponentType: componentType,
		TidbGroupId:   tidbGroupID,
		TenantId:      cluster.GetOrganizationId(),
		ProjectId:     cluster.GetProjectId(),
	})
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &clusterapi.GetHeterogeneousDeployResponse{
		Deploy: buildHeterogeneousDeploy(deploy),
	})
}

// DeleteHeterogeneousDeploy
//
//	@Id				ClusterServiceDeleteHeterogeneousDeploy
//	@Description	delete heterogeneous deploy for a cluster
//	@Tags			ClusterService
//	@Accept			json
//	@Produce		json
//	@Param			cluster_id	path		string										true	"cluster id"
//	@Param			req			body		clusterapi.DeleteHeterogeneousDeployRequest	true	"request param"
//	@Success		200			{object}	clusterapi.DeleteHeterogeneousDeployResponse
//	@Router			/api/v1/devops/clusters/{cluster_id}/hetero-deploy/delete [post]
func (c *ClusterController) DeleteHeterogeneousDeploy(ctx *gin.Context) {
	clusterID, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("cluster_id is not a int"), api.ErrCodeInvalidArgument)
		return
	}
	cluster, err := c.coreSvcCli.Mgmt.GetCluster(ctx, clusterID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

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

	req := &clusterapi.DeleteHeterogeneousDeployRequest{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	componentType, err := clusterapi.DedicatedComponentTypeFromAPI(req.ComponentType)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("invalid component_type: %s", req.ComponentType), api.ErrCodeInvalidArgument)
		return
	}
	var tidbGroupID uint64
	if req.TiDBGroupID != "" {
		tidbGroupID, err = strconv.ParseUint(req.TiDBGroupID, 10, 64)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("invalid tidb_group_id: %s", req.TiDBGroupID), api.ErrCodeInvalidArgument)
			return
		}
	}

	opReq := &mgmtv1.DeleteGKEHeterogeneousDeployRequest{
		ClusterId:     clusterID,
		ComponentType: componentType,
		TidbGroupId:   tidbGroupID,
		TenantId:      cluster.GetOrganizationId(),
		ProjectId:     cluster.GetProjectId(),
	}
	opReqData, err := protojson.Marshal(opReq)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("marshal proto message failed: %s", err), api.ErrCodeInternal)
		return
	}

	insID, err := c.operationSvc.CreateInstance(ctx, opsvcapi.CreateInstanceParam{
		Type:      opent.OperationTargetCluster,
		Operation: "DeleteHeterogeneousDeployForDedicatedCluster",
		Reason:    req.Reason,
		Applicant: email,
		Params: map[opent.ParamName]string{
			opent.ParamClusterID:                        strconv.FormatUint(clusterID, 10),
			opent.ParamDeleteHeterogeneousDeployRequest: string(opReqData),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvcclusteraction.ActionDeleteHeterogeneousDeploy.Name,
				StartPolicy: opent.ActionStartPolicyManual,
			},
		},
	})
	if err != nil {
		ctrl.Fail(ctx, fmt.Errorf("create operation instance failed: %w", err))
		return
	}

	ctrl.Success(ctx, &clusterapi.DeleteHeterogeneousDeployResponse{InstanceID: *insID})
}

func buildHeterogeneousDeploy(deploy *mgmtv1.GKEHeterogeneousDeploy) *clusterapi.HeterogeneousDeploy {
	podCounts := make([]clusterapi.HeterogeneousDeployPodCount, 0, len(deploy.GetPodCounts()))
	for _, podCount := range deploy.GetPodCounts() {
		podCounts = append(podCounts, clusterapi.HeterogeneousDeployPodCount{
			MachineType: podCount.GetMachineType(),
			PodCount:    podCount.GetPodCount(),
		})
	}

	return &clusterapi.HeterogeneousDeploy{
		Enabled:                 deploy.GetEnabled(),
		ComponentType:           clusterapi.APIComponentTypeFromDedicated(deploy.GetComponentType()),
		CurrentInstanceType:     deploy.GetCurrentInstanceType(),
		AlternativeInstanceType: deploy.GetAlternativeInstanceType(),
		TiDBGroupID:             strconv.FormatUint(deploy.GetTidbGroupId(), 10),
		Status:                  deploy.GetStatus(),
		PodCounts:               podCounts,
	}
}
