package cluster

import (
	"fmt"
	"strconv"

	"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"

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

// CreateMigrationCluster
//
//	@Id				ClusterServiceCreateMigrationCluster
//	@Description	create a cluster for migration
//	@Tags			ClusterService
//	@Accept			json
//	@Produce		json
//	@Param			cluster_id	path		string										true	"cluster id"
//	@Param			req			body		clusterapi.CreateMigrationClusterRequest	true	"request param"
//	@Success		200			{object}	clusterapi.CreateMigrationClusterResponse
//	@Router			/api/v1/devops/clusters/{cluster_id}/migration/create-migration-cluster [post]
func (c *ClusterController) CreateMigrationCluster(ctx *gin.Context) {
	cid, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

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

	// build the operation request
	opReq := &mgmtv1.CreateMigrationClusterRequest{
		MigrationCluster: &mgmtv1.MigrationCluster{
			SourceClusterId:              cid,
			ClusterDisplayName:           req.ClusterDisplayName,
			BrVersion:                    req.BrVersion,
			BrImageTag:                   req.BrImageTag,
			BrCustomOptions:              req.BrCustomOptions,
			TargetClusterVersion:         req.TargetClusterVersion,
			TidbImageTag:                 req.TiDBImageTag,
			TikvImageTag:                 req.TiKVImageTag,
			PdImageTag:                   req.PDImageTag,
			TiflashImageTag:              req.TiFlashImageTag,
			CreateClusterTidbImageTag:    req.CreateTiDBImageTag,
			CreateClusterTikvImageTag:    req.CreateTiKVImageTag,
			CreateClusterPdImageTag:      req.CreatePDImageTag,
			CreateClusterTiflashImageTag: req.CreateTiFlashImageTag,
		},
	}

	if req.BackupID != "" {
		backupID, err := strconv.ParseUint(req.BackupID, 10, 64)
		if err != nil {
			ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
			return
		}
		opReq.MigrationCluster.BackupId = backupID
	}

	if req.TargetProjectID != "" {
		targetProjectID, err := strconv.ParseUint(req.TargetProjectID, 10, 64)
		if err != nil {
			ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
			return
		}
		opReq.MigrationCluster.TargetProjectId = targetProjectID
	}

	if req.TargetRegionID != "" {
		targetRegionID, err := parseMigrationClusterRegionName(req.TargetRegionID)
		if err != nil {
			ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
			return
		}
		opReq.MigrationCluster.TargetRegionId = targetRegionID
	}

	if req.RestoreMode != "" {
		restoreMode, err := parseMigrationClusterRestoreMode(req.RestoreMode)
		if err != nil {
			ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
			return
		}
		opReq.MigrationCluster.RestoreMode = restoreMode
	}

	if req.BackupRegionID != "" {
		backupRegionID, err := parseMigrationClusterRegionName(req.BackupRegionID)
		if err != nil {
			ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
			return
		}
		opReq.MigrationCluster.BackupRegionId = backupRegionID
	}

	if req.ConfigSyncStrategy != nil {
		opReq.ConfigSyncStrategy = &mgmtv1.ConfigSyncStrategy{
			AllowFallbackToBackup: req.ConfigSyncStrategy.AllowFallbackToBackup,
			SkipSystemVariables:   req.ConfigSyncStrategy.SkipSystemVariables,
			SkipPdConfig:          req.ConfigSyncStrategy.SkipPDConfig,
			SkipPdServerConfig:    req.ConfigSyncStrategy.SkipPDServerConfig,
			SkipPlacementRules:    req.ConfigSyncStrategy.SkipPlacementRules,
			SkipStoreLimits:       req.ConfigSyncStrategy.SkipStoreLimits,
		}
	}

	if len(req.TiDBNodeSetting) != 0 {
		setting := &mgmtv1.MigrationCluster_TidbNodeSetting{
			NodeSpecKey: req.TiDBNodeSetting[0].NodeSpecKey,
		}
		for _, tn := range req.TiDBNodeSetting {
			if tn.NodeSpecKey != setting.NodeSpecKey {
				ctrl.FailWithCode(ctx, fmt.Errorf("node spec key cannot be different"), api.ErrCodeInvalidArgument)
				return
			}
			setting.TidbNodeGroups = append(setting.TidbNodeGroups, &mgmtv1.MigrationCluster_TidbNodeGroup{
				DisplayName:    tn.DisplayName,
				NodeCount:      tn.NodeCount,
				IsDefaultGroup: tn.IsDefaultGroup,
			})
		}
		opReq.MigrationCluster.TidbNodeSetting = setting
	}
	if req.TiKVNodeSetting != nil {
		opReq.MigrationCluster.TikvNodeSetting = &mgmtv1.MigrationCluster_StorageNodeSetting{
			NodeSpecKey: req.TiKVNodeSetting.NodeSpecKey,
			NodeCount:   req.TiKVNodeSetting.NodeCount,
		}
	}
	if req.TiFlashNodeSetting != nil {
		opReq.MigrationCluster.TiflashNodeSetting = &mgmtv1.MigrationCluster_StorageNodeSetting{
			NodeSpecKey: req.TiFlashNodeSetting.NodeSpecKey,
			NodeCount:   req.TiFlashNodeSetting.NodeCount,
		}
	}
	if req.PDNodeSetting != nil {
		opReq.MigrationCluster.PdNodeSetting = &mgmtv1.MigrationCluster_PdNodeSetting{
			NodeSpecKey: req.PDNodeSetting.NodeSpecKey,
		}
	}

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

	createOPParam := opsvcapi.CreateInstanceParam{
		Type:      opent.OperationTargetCluster,
		Operation: "CreateMigrationClusterForDedicatedCluster", // NOTE: MUST match the name in operation definition.
		Reason:    req.Reason,
		Applicant: email,
		Params: map[opent.ParamName]string{
			opent.ParamClusterID:                     strconv.FormatUint(cid, 10),
			opent.ParamCreateMigrationClusterRequest: string(opReqData),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvcclusteraction.ActionCreateMigrationCluster.Name,
				StartPolicy: opent.ActionStartPolicyManual,
			},
		},
	}

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

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

func parseMigrationClusterRegionName(regionName string) (commonv1.Region_Name, error) {
	value, ok := commonv1.Region_Name_value[regionName]
	if !ok {
		return commonv1.Region_NAME_UNSPECIFIED, fmt.Errorf("invalid region name: %s", regionName)
	}
	return commonv1.Region_Name(value), nil
}

func parseMigrationClusterRestoreMode(mode string) (mgmtv1.MigrationCluster_RestoreMode, error) {
	value, ok := mgmtv1.MigrationCluster_RestoreMode_value[mode]
	if !ok {
		return mgmtv1.MigrationCluster_MODE_UNSPECIFIED, fmt.Errorf("invalid restore mode: %s", mode)
	}
	return mgmtv1.MigrationCluster_RestoreMode(value), nil
}

// MigratePrivateLink
//
//	@Id				ClusterServiceMigratePrivateLink
//	@Description	migrate cluster private link
//	@Tags			ClusterService
//	@Accept			json
//	@Produce		json
//	@Param			cluster_id	path		string									true	"cluster id"
//	@Param			req			body		clusterapi.MigratePrivateLinkRequest	true	"request param"
//	@Success		200			{object}	clusterapi.MigratePrivateLinkResponse
//	@Router			/api/v1/devops/clusters/{cluster_id}/migration/migrate-privatelink [post]
func (c *ClusterController) MigratePrivateLink(ctx *gin.Context) {
	cid, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

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

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

	// build the operation request
	opReq := &mgmtv1.CreatePrivateLinkServiceMigrationRequest{
		Migration: &mgmtv1.PrivateLinkServiceMigration{
			SourceClusterId: cid,
		},
	}
	opReq.Migration.TargetClusterId, err = strconv.ParseUint(req.TargetClusterID, 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	for _, pair := range req.TiDBGroupPairs {
		opReq.Migration.MigrationTidbGroups = append(opReq.Migration.MigrationTidbGroups, &mgmtv1.PrivateLinkServiceMigration_MigrationTidbGroup{
			SourceTidbGroupId: pair.SourceTiDBGroupID,
			TargetTidbGroupId: pair.TargetTiDBGroupID,
		})
	}

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

	createOPParam := opsvcapi.CreateInstanceParam{
		Type:      opent.OperationTargetCluster,
		Operation: "MigratePrivateLinkForDedicatedCluster", // NOTE: MUST match the name in operation definition.
		Reason:    req.Reason,
		Applicant: email,
		Params: map[opent.ParamName]string{
			opent.ParamClusterID:                 strconv.FormatUint(cid, 10),
			opent.ParamMigratePrivateLinkRequest: string(opReqData),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        opsvcclusteraction.ActionMigrateClusterPrivateLink.Name,
				StartPolicy: opent.ActionStartPolicyManual,
			},
		},
	}

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

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