package server

import (
	"context"
	"strings"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	apicommon "github.com/tidbcloud/infra-provider/api/tidbcloud/common"
	"github.com/tidbcloud/infra-provider/pkg/gkeheterogeneous"
)

const defaultGKEHeterogeneousNodePoolMaxNodeCount int64 = 100

func (s *ClusterServer) GetGKEHeterogeneousNodePools(ctx context.Context, req *apiv1.GetGKEHeterogeneousNodePoolsRequest) (*apiv1.ListGKEHeterogeneousNodePoolsResponse, error) {
	cluster, err := s.gkeHeterogeneousCluster(ctx, req.GetClusterId())
	if err != nil {
		return nil, err
	}
	nodePools, err := s.gkeHeterogeneousNodePools(ctx, cluster, req.GetComponentType(), req.GetTidbGroupId())
	if err != nil {
		return nil, err
	}
	return &apiv1.ListGKEHeterogeneousNodePoolsResponse{NodePools: nodePools}, nil
}

func (s *ClusterServer) CreateGKEHeterogeneousNodePool(ctx context.Context, req *apiv1.CreateGKEHeterogeneousNodePoolRequest) (*apiv1.GKEHeterogeneousNodePool, error) {
	cluster, err := s.gkeHeterogeneousCluster(ctx, req.GetClusterId())
	if err != nil {
		return nil, err
	}
	return s.ensureGKENodePoolForCluster(ctx, cluster, req.GetInstanceType(), defaultGKEHeterogeneousNodePoolMaxNodeCount)
}

func (s *ClusterServer) ListGKEHeterogeneousNodePools(ctx context.Context, req *apiv1.ListGKEHeterogeneousNodePoolsRequest) (*apiv1.ListGKEHeterogeneousNodePoolsResponse, error) {
	cluster, err := s.gkeHeterogeneousCluster(ctx, req.GetClusterId())
	if err != nil {
		return nil, err
	}
	return s.listGKEHeterogeneousNodePoolInventory(ctx, cluster)
}

func (s *ClusterServer) listGKEHeterogeneousNodePoolInventory(ctx context.Context, cluster *cloudv1.Cluster) (*apiv1.ListGKEHeterogeneousNodePoolsResponse, error) {
	nodePools := make([]*apiv1.GKEHeterogeneousNodePool, 0)
	tikvIntentConfig, err := gkeHeterogeneousIntentConfigForComponent(apicommon.ComponentType_TIKV)
	if err != nil {
		return nil, err
	}
	if gkeHeterogeneousIntentConfigured(cluster, tikvIntentConfig, "") {
		tikvNodePools, err := s.gkeHeterogeneousNodePools(ctx, cluster, apicommon.ComponentType_TIKV, "")
		if err != nil {
			return nil, err
		}
		nodePools = append(nodePools, tikvNodePools...)
	}
	for _, groupID := range gkeheterogeneous.ActiveTiDBGroupIDs(cluster) {
		tidbNodePools, err := s.gkeHeterogeneousNodePools(ctx, cluster, apicommon.ComponentType_TIDB, groupID)
		if err != nil {
			return nil, err
		}
		nodePools = append(nodePools, tidbNodePools...)
	}
	nodePools, err = s.appendPreparedGKEHeterogeneousNodePools(ctx, cluster, nodePools)
	if err != nil {
		return nil, err
	}
	return &apiv1.ListGKEHeterogeneousNodePoolsResponse{NodePools: nodePools}, nil
}

func (s *ClusterServer) ensureGKENodePoolForCluster(ctx context.Context, cluster *cloudv1.Cluster, instanceType string, maxNodeCount int64) (*apiv1.GKEHeterogeneousNodePool, error) {
	instanceType = strings.TrimSpace(instanceType)
	if instanceType == "" {
		return nil, status.Error(codes.InvalidArgument, "instance_type is required")
	}
	nodePoolName := gkeheterogeneous.NodePoolName(cluster, instanceType)
	nodePool, err := s.ensureGKENodePoolResource(ctx, cluster, nodePoolName, instanceType, 0, maxNodeCount)
	if err != nil {
		return nil, err
	}
	resp := gkeHeterogeneousNodePoolResourceResponse(nodePool, "")
	resp.ClusterId = cluster.GetClusterID()
	resp.ApiManaged = true
	resp.Exists = true
	return resp, nil
}

func (s *ClusterServer) gkeHeterogeneousNodePools(ctx context.Context, cluster *cloudv1.Cluster, componentType apicommon.ComponentType, tidbGroupID string) ([]*apiv1.GKEHeterogeneousNodePool, error) {
	if componentType == apicommon.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
		return nil, status.Error(codes.InvalidArgument, "component_type is required")
	}

	intentConfig, tidbGroupID, err := resolveGKEHeterogeneousIntent(cluster, componentType, tidbGroupID)
	if err != nil {
		return nil, err
	}
	state, err := loadGKEHeterogeneousNodePoolBindingState(cluster, intentConfig, tidbGroupID)
	if err != nil {
		return nil, err
	}
	bindings := state.ExplicitBindings()
	if len(bindings) == 0 {
		return nil, nil
	}
	out := make([]*apiv1.GKEHeterogeneousNodePool, 0, len(bindings))
	computeClassName := strings.TrimSpace(state.computeClassName)
	for _, binding := range bindings {
		isBase := state.IsBaseBinding(binding)
		resp := &apiv1.GKEHeterogeneousNodePool{
			ClusterId:        cluster.GetClusterID(),
			ComponentType:    intentConfig.componentType,
			TidbGroupId:      tidbGroupID,
			InstanceType:     strings.TrimSpace(binding.InstanceType),
			NodePoolName:     strings.TrimSpace(binding.NodePoolName),
			ComputeClassName: computeClassName,
			IsBaseNodePool:   isBase,
			ApiManaged:       true,
		}
		nodePool, err := s.getGKEHeterogeneousNodePoolResource(ctx, binding.NodePoolName)
		if err != nil {
			if status.Code(err) == codes.NotFound {
				out = append(out, resp)
				continue
			}
			return nil, err
		}
		if err := validateGKEHeterogeneousNodePoolBindingResource(cluster, state, binding, nodePool); err != nil {
			return nil, err
		}
		resp.Exists = true
		resp.MinNodeCount, resp.MaxNodeCount = gkeHeterogeneousNodePoolAutoscalingCounts(nodePool)
		out = append(out, resp)
	}
	return out, nil
}
