package ngoperation

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	commonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	nextgenv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
)

var (
	ActionCreateResourcePool = opent.ActionMetadata{
		Name: "CreateResourcePool",
		Type: opent.ActionTypeAuto,
	}
)

var (
	_ actions.Action = &createResourcePool{}
)

var allowedPdDeploymentModes = map[string]nextgenv1.PdDeploymentMode{
	"MICROSERVICE": nextgenv1.PdDeploymentMode_PD_DEPLOYMENT_MODE_MICROSERVICE,
	"MONOLITHIC":   nextgenv1.PdDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
}

const allowedPdDeploymentModeValues = "MICROSERVICE, MONOLITHIC"

var allowedEnabledTiKVRemoteWorkers = map[string][]nextgenv1.ComponentType{
	"TIKV_WORKER,COPROCESSOR_WORKER": {
		nextgenv1.ComponentType_TIKV_WORKER,
		nextgenv1.ComponentType_COPROCESSOR_WORKER,
	},
	"TIKV_WORKER": {
		nextgenv1.ComponentType_TIKV_WORKER,
	},
}

const allowedEnabledTiKVRemoteWorkerValues = "TIKV_WORKER,COPROCESSOR_WORKER; TIKV_WORKER"

var allowedTiflashModes = map[string]nextgenv1.TiflashMode{
	"AUTO":     nextgenv1.TiflashMode_TIFLASH_MODE_AUTO,
	"LEGACY":   nextgenv1.TiflashMode_TIFLASH_MODE_LEGACY,
	"COLUMNAR": nextgenv1.TiflashMode_TIFLASH_MODE_COLUMNAR,
}

const allowedTiflashModeValues = "AUTO, LEGACY, COLUMNAR"

var allowedHighAvailabilityTypes = map[string]nextgenv1.ResourcePool_HighAvailabilityType{
	"REGIONAL": nextgenv1.ResourcePool_REGIONAL,
	"ZONAL":    nextgenv1.ResourcePool_ZONAL,
}

const allowedHighAvailabilityTypeValues = "REGIONAL, ZONAL"

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

func NewCreateResourcePool(comps *actions.Components) actions.AutoAction {
	return &createResourcePool{comps: comps}
}

func (a *createResourcePool) Metadata() opent.ActionMetadata {
	return ActionCreateResourcePool
}

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

	return nil
}

func (a *createResourcePool) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {

	req, err := a.getParam(instance)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	resp, err := a.comps.CoreSvc.Mgmt.ResourcePool.CreateResourcePool(ctx, req)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	return actions.RunningActionResult(fmt.Sprintf("sent request to create resource pool: %s", resp.ResourcePool), nil), nil
}

func (a *createResourcePool) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	return actions.SucceededActionResult(nil), nil
}

func (a *createResourcePool) getParam(instance *opent.Instance) (*nextgenv1.CreateResourcePoolRequest, error) {
	paramMap := make(map[opent.ParamName]string)
	paramList := []opent.ParamName{
		opent.ParamProvider,
		opent.ParamRegionName,
		opent.ParamCIDR,
		opent.ParamServicePlan,
		opent.ParamName("name"),
		opent.ParamK8SServiceCIDR,
		opent.ParamPCAArn,
		opent.ParamProviderIdentity,
	}
	optionalParamSet := map[opent.ParamName]bool{
		opent.ParamPCAArn:           true,
		opent.ParamProviderIdentity: true,
	}
	uintParamMap := make(map[opent.ParamName]uint64)
	uintParamList := []opent.ParamName{
		opent.ParamTenantID,
		opent.ParamInitialRcu,
		opent.ParamInstanceLimit,
		opent.ParamVcpusLimit,
	}

	for _, disk := range []string{"tikv_data", "tikv_raftlog", "tiflash_compute", "tiflash_write"} {
		paramList = append(paramList, opent.ParamName(fmt.Sprintf("%s.%s", disk, opent.ParamVolumeType)))
		for _, param := range []opent.ParamName{opent.ParamVolumeSizeGi, opent.ParamVolumeIops, opent.ParamVolumeThroughputMbs} {
			uintParamList = append(uintParamList, opent.ParamName(fmt.Sprintf("%s.%s", disk, param)))
		}
	}
	for _, param := range paramList {
		value, exist := instance.GetParam(param)
		if !exist && !optionalParamSet[param] {
			return nil, fmt.Errorf("param %s is missing for creating resource pool", param)
		}
		paramMap[param] = value
	}
	for _, param := range uintParamList {
		val, exist := instance.GetParam(param)
		uintV := uint64(0)
		if exist {
			uintV, _ = strconv.ParseUint(val, 10, 64)
		}
		uintParamMap[param] = uintV
	}
	servicePlanValue, ok := commonv1.ServicePlan_value[paramMap[opent.ParamServicePlan]]
	if !ok {
		return nil, fmt.Errorf("invalid service plan %s for creating resource pool", paramMap[opent.ParamServicePlan])
	}
	servicePlan := commonv1.ServicePlan(servicePlanValue)
	tenantID := uintParamMap[opent.ParamTenantID]
	isSharedResourcePool := tenantID == 0
	if !isSharedResourcePool && !isOriginalResourcePoolServicePlan(servicePlan) {
		return nil, fmt.Errorf("service plan %s is not supported for creating resource pool", servicePlan.String())
	}

	ownerType := nextgenv1.ResourcePool_ORGANIZATION
	if isSharedResourcePool {
		ownerType = nextgenv1.ResourcePool_SHARED
	}

	metaProfileID, ok := instance.GetParam(opent.ParamMetaProfileID)
	if !ok || metaProfileID == "" {
		metaProfileID = ""
	}
	p := &nextgenv1.CreateResourcePoolRequest{
		ResourcePool: &nextgenv1.ResourcePool{
			Name:                paramMap[opent.ParamName("name")],
			ProviderId:          commonv1.CloudProvider_Name(commonv1.CloudProvider_Name_value[paramMap[opent.ParamProvider]]),
			RegionId:            toMgmtRegionID(paramMap[opent.ParamProvider], paramMap[opent.ParamRegionName]),
			OwnerType:           ownerType,
			OwnerId:             tenantID,
			Cidr:                paramMap[opent.ParamCIDR],
			ServicePlan:         servicePlan,
			InstanceLimit:       uintParamMap[opent.ParamInstanceLimit],
			VcpusLimit:          uintParamMap[opent.ParamVcpusLimit],
			K8SServiceCidr:      paramMap[opent.ParamK8SServiceCIDR],
			PcaArn:              paramMap[opent.ParamPCAArn],
			ProviderIdentity:    paramMap[opent.ParamProviderIdentity],
			ConfigMetaProfileId: metaProfileID,
		},
		Demand: &nextgenv1.CreateResourcePoolRequest_Demand{
			InitialRcu:  uint32(uintParamMap[opent.ParamInitialRcu]),
			TikvData:    buildVolumeParams(paramMap, uintParamMap, "tikv_data"),
			TikvRaftlog: buildVolumeParams(paramMap, uintParamMap, "tikv_raftlog"),
			TiflashCn:   buildVolumeParams(paramMap, uintParamMap, "tiflash_compute"),
			TiflashWn:   buildVolumeParams(paramMap, uintParamMap, "tiflash_write"),
		},
	}
	if placementGroupEnabledValue, exist := instance.GetParam(opent.ParamPlacementGroupEnabled); exist && placementGroupEnabledValue != "" {
		placementGroupEnabled, err := strconv.ParseBool(placementGroupEnabledValue)
		if err != nil {
			return nil, fmt.Errorf("invalid placement_group_enabled %s for creating resource pool", placementGroupEnabledValue)
		}
		p.ResourcePool.PlacementGroupEnabled = placementGroupEnabled
	}
	if enableCapacityPlannerValue, exist := instance.GetParam(opent.ParamEnableCapacityPlanner); exist && enableCapacityPlannerValue != "" {
		enableCapacityPlanner, err := strconv.ParseBool(enableCapacityPlannerValue)
		if err != nil {
			return nil, fmt.Errorf("invalid %s %s for creating resource pool", opent.ParamEnableCapacityPlanner, enableCapacityPlannerValue)
		}
		p.EnableCapacityPlanner = enableCapacityPlanner
	}
	if pdDeploymentModeValue, exist := instance.GetParam(opent.ParamPdDeploymentMode); exist && pdDeploymentModeValue != "" {
		pdDeploymentMode, ok := allowedPdDeploymentModes[pdDeploymentModeValue]
		if !ok {
			return nil, fmt.Errorf("invalid %s %s for creating resource pool, allowed values: %s", opent.ParamPdDeploymentMode, pdDeploymentModeValue, allowedPdDeploymentModeValues)
		}
		p.PdDeploymentMode = pdDeploymentMode
	}
	if enabledTiKVRemoteWorkersValue, exist := instance.GetParam(opent.ParamEnabledTiKVRemoteWorkers); exist && enabledTiKVRemoteWorkersValue != "" {
		enabledTiKVRemoteWorkers, ok := allowedEnabledTiKVRemoteWorkers[enabledTiKVRemoteWorkersValue]
		if !ok {
			return nil, fmt.Errorf("invalid %s %s for creating resource pool, allowed values: %s", opent.ParamEnabledTiKVRemoteWorkers, enabledTiKVRemoteWorkersValue, allowedEnabledTiKVRemoteWorkerValues)
		}
		p.EnabledTikvRemoteWorkers = append([]nextgenv1.ComponentType(nil), enabledTiKVRemoteWorkers...)
	}
	if tiflashModeValue, exist := instance.GetParam(opent.ParamTiflashMode); exist && tiflashModeValue != "" {
		tiflashMode, ok := allowedTiflashModes[tiflashModeValue]
		if !ok {
			return nil, fmt.Errorf("invalid %s %s for creating resource pool, allowed values: %s", opent.ParamTiflashMode, tiflashModeValue, allowedTiflashModeValues)
		}
		p.TiflashMode = tiflashMode
	}
	if highAvailabilityTypeValue, exist := instance.GetParam(opent.ParamHighAvailabilityType); exist && highAvailabilityTypeValue != "" {
		highAvailabilityType, ok := allowedHighAvailabilityTypes[highAvailabilityTypeValue]
		if !ok {
			return nil, fmt.Errorf("invalid %s %s for creating resource pool, allowed values: %s", opent.ParamHighAvailabilityType, highAvailabilityTypeValue, allowedHighAvailabilityTypeValues)
		}
		p.ResourcePool.HighAvailabilityType = highAvailabilityType
	}
	if nodeZonesValue, exist := instance.GetParam(opent.ParamNodeZones); exist && nodeZonesValue != "" {
		parts := strings.Split(nodeZonesValue, ",")
		nodeZones := make([]string, 0, len(parts))
		for _, part := range parts {
			trimmed := strings.TrimSpace(part)
			if trimmed == "" {
				continue
			}
			nodeZones = append(nodeZones, trimmed)
		}
		if len(nodeZones) == 0 {
			return nil, fmt.Errorf("invalid %s %s for creating resource pool, must contain at least one non-empty zone", opent.ParamNodeZones, nodeZonesValue)
		}
		if p.ResourcePool.ZoneConfig == nil {
			p.ResourcePool.ZoneConfig = &nextgenv1.ResourcePool_ZoneConfig{}
		}
		p.ResourcePool.ZoneConfig.NodeZones = nodeZones
	}
	// logical_region only takes effect for Essential_V2 resource pools; it is ignored for other plans.
	if logicalRegion, exist := instance.GetParam(opent.ParamLogicalRegion); exist && logicalRegion != "" && servicePlan == commonv1.ServicePlan_Essential_V2 {
		p.LogicalRegion = logicalRegion
	}
	return p, nil
}

func buildVolumeParams(paramMap map[opent.ParamName]string, uintParamMap map[opent.ParamName]uint64, diskPrefix string) *nextgenv1.CreateResourcePoolRequest_Storage {
	volumeTypeStr := paramMap[opent.ParamName(fmt.Sprintf("%s.%s", diskPrefix, opent.ParamVolumeType))]
	diskSizeGi := uintParamMap[opent.ParamName(fmt.Sprintf("%s.%s", diskPrefix, opent.ParamVolumeSizeGi))]
	diskIops := uintParamMap[opent.ParamName(fmt.Sprintf("%s.%s", diskPrefix, opent.ParamVolumeIops))]
	diskThroughputMbps := uintParamMap[opent.ParamName(fmt.Sprintf("%s.%s", diskPrefix, opent.ParamVolumeThroughputMbs))]

	return &nextgenv1.CreateResourcePoolRequest_Storage{
		StorageType:        nextgenv1.ResourcePoolProfile_StorageType(nextgenv1.ResourcePoolProfile_StorageType_value[volumeTypeStr]),
		DiskSizeGi:         uint32(diskSizeGi),
		DiskIops:           uint32(diskIops),
		DiskThroughputMbps: uint32(diskThroughputMbps),
	}
}
