package gkeheterogeneous

import (
	"fmt"
	"slices"
	"strconv"
	"strings"

	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func ValidateInstanceTypes(
	deploy *regionaldedicatedv1.GKEHeterogeneousDeploy,
	currentInstanceType string,
	alternativeInstanceType string,
) bizerr.BizError {
	instanceTypes := deploy.GetInstanceTypes()
	if len(instanceTypes) == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "existing gke heterogeneous deploy has no instance types")
	}
	if instanceTypes[0] != currentInstanceType || !slices.Contains(instanceTypes, alternativeInstanceType) {
		return bizerr.New(bizerr.ErrCodeParams, "cannot change instance types when gke heterogeneous deploy exists")
	}
	return nil
}

func ToGlobalDeploy(
	deploy *regionaldedicatedv1.GKEHeterogeneousDeploy,
	nodePool *regionaldedicatedv1.GKEHeterogeneousNodePool,
	currentInstanceType string,
	alternativeInstanceType string,
) (*globaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError) {
	if deploy == nil {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "gke heterogeneous deploy is nil")
	}
	instanceTypes := deploy.GetInstanceTypes()
	if currentInstanceType == "" && len(instanceTypes) > 0 {
		currentInstanceType = instanceTypes[0]
	}
	if alternativeInstanceType == "" {
		if len(instanceTypes) > 1 {
			alternativeInstanceType = instanceTypes[len(instanceTypes)-1]
		} else if nodePool != nil && nodePool.GetInstanceType() != "" {
			alternativeInstanceType = nodePool.GetInstanceType()
		}
	}
	var tidbGroupID uint64
	if deploy.GetTidbGroupId() != "" {
		parsedTidbGroupID, err := strconv.ParseUint(deploy.GetTidbGroupId(), 10, 64)
		if err != nil {
			return nil, bizerr.New(
				bizerr.ErrCodeRegionalAPI,
				"invalid tidb_group_id %q in gke heterogeneous deploy",
				deploy.GetTidbGroupId(),
			)
		}
		tidbGroupID = parsedTidbGroupID
	}
	podCounts := make([]*globaldedicatedv1.GKEHeterogeneousDeployPodCount, 0, len(deploy.GetPodCounts()))
	for _, pc := range deploy.GetPodCounts() {
		podCounts = append(podCounts, &globaldedicatedv1.GKEHeterogeneousDeployPodCount{
			MachineType: pc.GetMachineType(),
			PodCount:    pc.GetPodCount(),
		})
	}
	return &globaldedicatedv1.GKEHeterogeneousDeploy{
		Enabled:                 deploy.GetEnabled(),
		ComponentType:           ToGlobalComponentType(deploy.GetComponentType()),
		CurrentInstanceType:     currentInstanceType,
		AlternativeInstanceType: alternativeInstanceType,
		TidbGroupId:             tidbGroupID,
		Status:                  deploymentsStatusString(deploy.GetStatus()),
		PodCounts:               podCounts,
	}, nil
}

func ToRegionalComponentTypeOrError(componentType globaldedicatedv1.ComponentType) (regionaldedicatedv1.ComponentType, bizerr.BizError) {
	regionalComponent, ok := ToRegionalComponentType(componentType)
	if !ok {
		return regionaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED,
			bizerr.New(bizerr.ErrCodeParams, "unsupported component_type %s", componentType.String())
	}
	return regionalComponent, nil
}

func ToGlobalComponentType(componentType regionaldedicatedv1.ComponentType) globaldedicatedv1.ComponentType {
	switch componentType {
	case regionaldedicatedv1.ComponentType_TIKV:
		return globaldedicatedv1.ComponentType_TIKV
	case regionaldedicatedv1.ComponentType_TIDB:
		return globaldedicatedv1.ComponentType_TIDB
	case regionaldedicatedv1.ComponentType_TIFLASH:
		return globaldedicatedv1.ComponentType_TIFLASH
	case regionaldedicatedv1.ComponentType_TIFLASH_LEARNER:
		return globaldedicatedv1.ComponentType_TIFLASH_LEARNER
	case regionaldedicatedv1.ComponentType_PD:
		return globaldedicatedv1.ComponentType_PD
	case regionaldedicatedv1.ComponentType_TIPROXY:
		return globaldedicatedv1.ComponentType_TIPROXY
	default:
		return globaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED
	}
}

func optionalUint64String(value uint64) string {
	if value == 0 {
		return ""
	}
	return strconv.FormatUint(value, 10)
}

func IsSupportedRegionalHeterogeneousComponentType(componentType globaldedicatedv1.ComponentType) bool {
	_, ok := ToRegionalComponentType(componentType)
	if !ok {
		return false
	}
	switch componentType {
	case globaldedicatedv1.ComponentType_TIKV, globaldedicatedv1.ComponentType_TIDB:
		return true
	default:
		return false
	}
}

func validateApplyRequest(req *globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest) bizerr.BizError {
	if !IsSupportedRegionalHeterogeneousComponentType(req.GetComponentType()) {
		return bizerr.New(bizerr.ErrCodeParams,
			"unsupported component_type %s for gke heterogeneous deploy", req.GetComponentType().String())
	}
	currentInstanceType := strings.TrimSpace(req.GetCurrentInstanceType())
	if currentInstanceType == "" {
		return bizerr.New(bizerr.ErrCodeParams, "current_instance_type is required")
	}
	alternativeInstanceType := strings.TrimSpace(req.GetAlternativeInstanceType())
	if alternativeInstanceType == "" {
		return bizerr.New(bizerr.ErrCodeParams, "alternative_instance_type is required")
	}
	if currentInstanceType == alternativeInstanceType {
		return bizerr.New(bizerr.ErrCodeParams, "current_instance_type and alternative_instance_type must be different")
	}
	req.CurrentInstanceType = currentInstanceType
	req.AlternativeInstanceType = alternativeInstanceType
	if berr := validateTidbGroupForComponent(req.GetComponentType(), req.GetTidbGroupId()); berr != nil {
		return berr
	}
	return nil
}

func validateTidbGroupForComponent(componentType globaldedicatedv1.ComponentType, tidbGroupID uint64) bizerr.BizError {
	if componentType == globaldedicatedv1.ComponentType_TIDB && tidbGroupID == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "tidb_group_id is required for tidb component")
	}
	if componentType != globaldedicatedv1.ComponentType_TIDB && tidbGroupID != 0 {
		return bizerr.New(bizerr.ErrCodeParams, "tidb_group_id must be 0 for non-tidb component")
	}
	return nil
}

func deploymentsStatusString(status regionaldedicatedv1.GKEHeterogeneousDeployStatus) string {
	s := status.String()
	if s == "" {
		s = fmt.Sprintf("%d", int32(status))
	}
	return s
}
