package nodepool

import (
	"fmt"

	gkev1beta1 "github.com/crossplane-contrib/provider-gcp/apis/container/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/pointer"

	infrav1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	compatcst "github.com/tidbcloud/infra-provider/pkg/compat/consts"
	compatnamer "github.com/tidbcloud/infra-provider/pkg/compat/namer"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/util/collections"
)

func buildNodePoolName(ngSetName string, network *cloudv1.Network) string {
	return namer.UniqueIdentifier(network.ObjectMeta, namer.WithPrefix(ngSetName), namer.WithLimit(40))
}

func toGKENodeTaints(ngSpec *cloudv1.NodegroupSpec) []*gkev1beta1.NodeTaint {
	var taints []*gkev1beta1.NodeTaint
	for _, taint := range ngSpec.Taints {
		effect := util.ToCrossplaneTaintEffect(taint.Effect)
		if effect == "" {
			continue
		}
		taints = append(taints, &gkev1beta1.NodeTaint{
			Effect: effect,
			Key:    taint.Key,
			Value:  taint.Value,
		})
	}
	return taints
}

// this is compat func for GetNodePoolByNGSpecAndNetWork. we use compat namer in this func
func GetNodePoolByNGSpecAndNetWork(ngSpec cloudv1.NodegroupSpec, network *cloudv1.Network,
	gcpProjectName string) (np *gkev1beta1.NodePool, npMut func() error) {
	refName := namer.UniqueIdentifier(network.ObjectMeta)
	poolName := buildNodePoolName(ngSpec.Name, network)
	tags := ngSpec.Labels
	tags = util.AddResourceName(tags, poolName)
	taints := toGKENodeTaints(&ngSpec)
	np = &gkev1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Name: poolName,
		},
	}
	npMut = func() error {
		if !np.DeletionTimestamp.IsZero() {
			return fmt.Errorf("node pool %s is deleted", np.Name)
		}
		np.Labels = collections.MergeMaps(np.Labels, ngSpec.Labels)
		np.Annotations = collections.MergeMaps(np.Annotations, ngSpec.Annotations)
		size, err := util.ParseDiskSizeAsGiB(ngSpec.MachineSpec.SystemVolumeSize)
		if err != nil {
			return err
		}
		np.Spec = gkev1beta1.NodePoolSpec{
			// TODO: current we let the following task can be passed, we will optimize the configuration later.
			ForProvider: gkev1beta1.NodePoolParameters{
				Autoscaling: &gkev1beta1.NodePoolAutoscaling{
					Enabled:      pointer.Bool(true),
					MaxNodeCount: pointer.Int64(int64(ngSpec.Maximum)),
					MinNodeCount: pointer.Int64(int64(ngSpec.Minimum)),
				},
				Config: &gkev1beta1.NodeConfig{
					DiskSizeGb:  pointer.Int64(size),
					DiskType:    pointer.String("pd-standard"), // todo
					ImageType:   pointer.String("cos_containerd"),
					Labels:      tags,
					MachineType: &ngSpec.MachineSpec.MachineType,
					OauthScopes: []string{
						"https://www.googleapis.com/auth/cloud-platform",
					},
					Taints: taints,
				},
				Locations:        ngSpec.Zones,
				InitialNodeCount: pointer.Int64(0),
				Management: &gkev1beta1.NodeManagementSpec{
					// Do it manually
					AutoUpgrade: pointer.Bool(false),
					AutoRepair:  pointer.Bool(false),
				},
			},
			ResourceSpec: xpv1.ResourceSpec{ProviderConfigReference: &xpv1.Reference{Name: refName}},
		}
		//In the old path, we used the same crossplane to create the NodePool and GKE clusters,
		//we set the ClusterRef field to let CrossPlane fetch metadata from the manages GKE resources.
		//but in the new path, the NodePool and the GKE are no longer managed through the same CrossPlane,
		//so we need to set the Cluster field to let CrossPlane query the Google API for GKE metadata
		if _, ok := network.Annotations[compatcst.CompatFlagAnnotation]; ok {
			clusterName, err := buildCrossPlaneNodePoolClusterName(network)
			if err != nil {
				return err
			}
			np.Spec.ForProvider.Cluster = clusterName
		} else {
			// use clusterRef in old path
			np.Spec.ForProvider.ClusterRef = &xpv1.Reference{Name: util.GetAnnotation(network, consts.AnnotationKeyDataplaneName)}
		}
		if ngSpec.NodeRole != nil {
			np.Spec.ForProvider.Config.ServiceAccount = ngSpec.NodeRole
		} else {
			sa := compatnamer.LegacyBuildGCPNodePoolServiceAccountEmailName(network, gcpProjectName)
			np.Spec.ForProvider.Config.ServiceAccount = &sa
		}
		return nil
	}
	return np, npMut
}

func buildCrossPlaneNodePoolClusterName(network *cloudv1.Network) (string, error) {
	networkspec, err := infrav1.GKENetworkSpecForNetwork(network)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("projects/%s/locations/%s/clusters/%s", networkspec.ProjectID, network.Spec.Region, util.GetAnnotation(network, consts.AnnotationKeyDataplaneName)), nil
}
