package clusteraction

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	dediv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

var (
	ActionModifyClusterVolume opent.ActionMetadata = opent.ActionMetadata{
		Name: "ModifyClusterVolume",
		Type: opent.ActionTypeAuto,
	}
)

type modifyClusterVolume struct {
	actions.BaseAction
	comps *actions.Components
}

func NewModifyClusterVolume(comps *actions.Components) actions.AutoAction {
	return &modifyClusterVolume{comps: comps}
}

func (a *modifyClusterVolume) Metadata() opent.ActionMetadata {
	return ActionModifyClusterVolume
}

func (a *modifyClusterVolume) PreCheck(ctx context.Context, instance *opent.Instance) error {
	_, err := a.getParam(ctx, instance)
	if err != nil {
		return err
	}

	cid, err := actions.GetClusterID(instance)
	if err != nil {
		return err
	}

	_, err = a.comps.CoreSvc.GetCluster(ctx, cid, false)
	if err != nil {
		return err
	}

	return nil
}

func (a *modifyClusterVolume) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	req, err := a.getParam(ctx, instance)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	_, err = a.comps.CoreSvc.Mgmt.Cluster.ModifyClusterVolume(ctx, req)
	if err != nil {
		return nil, err
	}

	return actions.RunningActionResult("sent request to modify cluster volume", nil), nil
}

func (a *modifyClusterVolume) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	cid, err := actions.GetClusterID(instance)
	if err != nil {
		return nil, err
	}

	cluster, err := a.comps.CoreSvc.GetCluster(ctx, cid, false)
	if err != nil {
		return actions.NewActionResult(opent.ActionStatusUnknown, err.Error(), nil), nil
	}

	if cluster.Status == core.ClusterStatus_CLUSTER_STATUS_NORMAL {
		return actions.SucceededActionResult(nil), nil
	}

	return actions.RunningActionResult(fmt.Sprintf("cluster status is '%s'", cluster.Status), nil), nil
}

func (a *modifyClusterVolume) getParam(ctx context.Context, instance *opent.Instance) (*mgmtv1.ModifyClusterVolumeRequest, error) {
	cid, err := actions.GetClusterID(instance)
	if err != nil {
		return nil, err
	}
	cluster, err := a.comps.CoreSvc.GetCluster(ctx, cid, false)
	if err != nil {
		return nil, err
	}

	newArch := false
	if cluster.Attributes != nil && cluster.Attributes.UseNewDedicatedArch != nil {
		newArch = *cluster.Attributes.UseNewDedicatedArch
	}
	if !newArch {
		return nil, fmt.Errorf("only 3-layer cluster supported")
	}

	componentType, _ := instance.GetParam(opent.ParamComponentType)
	if componentType == "" {
		return nil, fmt.Errorf("scale param component_type is required")
	}
	ct := getComponentType(componentType)

	storageTypeStr, _ := instance.GetParam(opent.ParamStorageType)
	if storageTypeStr == "" {
		return nil, fmt.Errorf("scale param storage_type is required")
	}
	storageType := getStorageType(storageTypeStr)

	req := &mgmtv1.ModifyClusterVolumeRequest{
		ClusterId:     cid,
		ComponentType: ct,
		DiskType:      storageType,
	}

	storageSizeStr, _ := instance.GetParam(opent.ParamVolumeSize)
	if storageSizeStr != "" {
		storageSize, err := strconv.ParseUint(storageSizeStr, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("parse storage size failed, error: %v", err)
		}
		size := uint32(storageSize)
		req.StorageSize = &size
	}

	iopsStr, _ := instance.GetParam(opent.ParamVolumeIops)
	if iopsStr != "" {
		iops, err := strconv.ParseUint(iopsStr, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("parse storage iops failed, error: %v", err)
		}
		num := uint32(iops)
		req.StorageIops = &num
	}

	additionalIopsStr, _ := instance.GetParam(opent.ParamAdditionalVolumeIops)
	if additionalIopsStr != "" {
		additionalIops, err := strconv.ParseUint(additionalIopsStr, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("parse storage additional iops failed, error: %v", err)
		}
		num := uint32(additionalIops)
		req.AdditionalIops = &num
	}

	throughputStr, _ := instance.GetParam(opent.ParamVolumeThroughput)
	if throughputStr != "" {
		throughput, err := strconv.ParseUint(throughputStr, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("parse storage throughput failed, error: %v", err)
		}
		num := uint32(throughput)
		req.StorageThroughput = &num
	}

	volumeTypeStr, _ := instance.GetParam(opent.ParamVolumeType)
	if volumeTypeStr != "" {
		volumeType := getVolumeType(volumeTypeStr)
		req.VolumeType = &volumeType
	}

	return req, nil
}

func getComponentType(componentType string) dediv1.ComponentType {
	switch componentType {
	case "tidb":
		return dediv1.ComponentType_TIDB
	case "tikv":
		return dediv1.ComponentType_TIKV
	case "tiflash":
		return dediv1.ComponentType_TIFLASH
	case "pd":
		return dediv1.ComponentType_PD
	default:
		return dediv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED
	}
}

func getStorageType(storageTyp string) dediv1.Sku_DiskType {
	switch storageTyp {
	case "data":
		return dediv1.Sku_DATA
	case "raft_log":
		return dediv1.Sku_RAFT_LOG
	default:
		return dediv1.Sku_DISK_TYPE_UNSPECIFIED
	}
}

func getVolumeType(volumeType string) dediv1.StorageMu_VolumeType {
	switch volumeType {
	case "gp3":
		return dediv1.StorageMu_GP3
	case "io2":
		return dediv1.StorageMu_IO2
	case "pd-ssd":
		return dediv1.StorageMu_PD_SSD
	case "premiumv2":
		return dediv1.StorageMu_PREMIUMV2
	case "ultrassd":
		return dediv1.StorageMu_ULTRASSD
	default:
		return dediv1.StorageMu_VOLUME_TYPE_UNSPECIFIED
	}
}
