package gkeheterogeneous

import (
	"context"
	"slices"
	"strings"

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

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type NodePoolBindingSet struct {
	NodePoolNames []string
	InstanceTypes []string
}

func FindNodePool(
	ctx context.Context,
	regionServerCli regionserver.Client,
	regionEn *entity.Region,
	clusterID string,
	componentType regionaldedicatedv1.ComponentType,
	tidbGroupID string,
	deploy *regionaldedicatedv1.GKEHeterogeneousDeploy,
	alternativeInstanceType string,
) (*regionaldedicatedv1.GKEHeterogeneousNodePool, bizerr.BizError) {
	resp, berr := regionServerCli.GetGKEHeterogeneousNodePools(ctx, regionEn, &regionaldedicatedv1.GetGKEHeterogeneousNodePoolsRequest{
		ClusterId:     clusterID,
		ComponentType: componentType,
		TidbGroupId:   tidbGroupID,
	})
	if berr != nil {
		return nil, berr
	}
	var nodePoolNames []string
	if deploy != nil {
		nodePoolNames = deploy.GetNodePoolNames()
	}
	for _, np := range resp.GetNodePools() {
		if len(nodePoolNames) > 0 && !slices.Contains(nodePoolNames, np.GetNodePoolName()) {
			continue
		}
		if alternativeInstanceType != "" && np.GetInstanceType() != alternativeInstanceType {
			continue
		}
		return np, nil
	}
	return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "gke heterogeneous node pool not found")
}

func FindExistingDeployNodePoolTarget(
	ctx context.Context,
	regionServerCli regionserver.Client,
	regionEn *entity.Region,
	clusterID string,
	componentType regionaldedicatedv1.ComponentType,
	tidbGroupID string,
	deploy *regionaldedicatedv1.GKEHeterogeneousDeploy,
	alternativeInstanceType string,
) (*regionaldedicatedv1.GKEHeterogeneousNodePool, bizerr.BizError) {
	resp, berr := regionServerCli.GetGKEHeterogeneousNodePools(ctx, regionEn, &regionaldedicatedv1.GetGKEHeterogeneousNodePoolsRequest{
		ClusterId:     clusterID,
		ComponentType: componentType,
		TidbGroupId:   tidbGroupID,
	})
	if berr != nil {
		return nil, berr
	}
	return validateExistingDeployNodePoolsAndFindAlternative(resp.GetNodePools(), deploy, alternativeInstanceType)
}

func validateExistingDeployNodePoolsAndFindAlternative(
	nodePools []*regionaldedicatedv1.GKEHeterogeneousNodePool,
	deploy *regionaldedicatedv1.GKEHeterogeneousDeploy,
	alternativeInstanceType string,
) (*regionaldedicatedv1.GKEHeterogeneousNodePool, bizerr.BizError) {
	if deploy == nil {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "gke heterogeneous deploy is nil")
	}
	boundNodePoolNames := deploy.GetNodePoolNames()
	if len(boundNodePoolNames) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "existing gke heterogeneous deploy has no bound node pools")
	}

	nodePoolsByName := make(map[string]*regionaldedicatedv1.GKEHeterogeneousNodePool, len(nodePools))
	for _, nodePool := range nodePools {
		name := strings.TrimSpace(nodePool.GetNodePoolName())
		if name == "" {
			continue
		}
		if _, ok := nodePoolsByName[name]; !ok {
			nodePoolsByName[name] = nodePool
		}
	}

	for _, boundName := range boundNodePoolNames {
		boundName = strings.TrimSpace(boundName)
		if boundName == "" {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "existing gke heterogeneous deploy has empty bound node pool name")
		}
		nodePool, ok := nodePoolsByName[boundName]
		if !ok {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "bound gke heterogeneous node pool %s not found", boundName)
		}
		if !nodePool.GetExists() {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "bound gke heterogeneous node pool %s does not exist", boundName)
		}
	}

	for _, boundName := range boundNodePoolNames {
		nodePool := nodePoolsByName[strings.TrimSpace(boundName)]
		if nodePool == nil {
			continue
		}
		if alternativeInstanceType != "" && !strings.EqualFold(nodePool.GetInstanceType(), alternativeInstanceType) {
			continue
		}
		return nodePool, nil
	}
	return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "gke heterogeneous node pool not found")
}

func EnsureSharedNodePool(
	ctx context.Context,
	regionServerCli regionserver.Client,
	regionEn *entity.Region,
	clusterID string,
	nodePoolName string,
	alternativeInstanceType string,
) (*regionaldedicatedv1.GKEHeterogeneousNodePool, bizerr.BizError) {
	nodePool, berr := regionServerCli.CreateGKEHeterogeneousNodePool(
		ctx,
		regionEn,
		&regionaldedicatedv1.CreateGKEHeterogeneousNodePoolRequest{
			ClusterId:    clusterID,
			InstanceType: alternativeInstanceType,
		},
	)
	if berr != nil {
		return nil, berr
	}
	if nodePoolName != "" && nodePool.GetNodePoolName() != nodePoolName {
		return nil, bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"gke heterogeneous node pool name mismatch: got %s, want %s",
			nodePool.GetNodePoolName(),
			nodePoolName,
		)
	}
	return nodePool, nil
}

func NodePoolBindings(
	baseNodePool *regionaldedicatedv1.GKEHeterogeneousNodePool,
	alternativeNodePool *regionaldedicatedv1.GKEHeterogeneousNodePool,
	currentInstanceType string,
	alternativeInstanceType string,
) (*NodePoolBindingSet, bizerr.BizError) {
	if baseNodePool == nil || baseNodePool.GetNodePoolName() == "" {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "empty base gke node pool from regional-server")
	}
	if alternativeNodePool == nil || alternativeNodePool.GetNodePoolName() == "" {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "empty alternative gke node pool from regional-server")
	}
	if baseNodePool.GetNodePoolName() == alternativeNodePool.GetNodePoolName() {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "base and alternative gke node pools must be different")
	}
	if baseNodePool.GetInstanceType() != "" && !strings.EqualFold(baseNodePool.GetInstanceType(), currentInstanceType) {
		return nil, bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"base gke node pool instance type mismatch: got %s, want %s",
			baseNodePool.GetInstanceType(),
			currentInstanceType,
		)
	}
	if alternativeNodePool.GetInstanceType() != "" && !strings.EqualFold(alternativeNodePool.GetInstanceType(), alternativeInstanceType) {
		return nil, bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"alternative gke node pool instance type mismatch: got %s, want %s",
			alternativeNodePool.GetInstanceType(),
			alternativeInstanceType,
		)
	}
	return &NodePoolBindingSet{
		NodePoolNames: []string{baseNodePool.GetNodePoolName(), alternativeNodePool.GetNodePoolName()},
		InstanceTypes: []string{currentInstanceType, alternativeInstanceType},
	}, nil
}
