package server

import (
	"context"
	"testing"

	"github.com/crossplane-contrib/provider-aws/apis/autoscaling/v1beta1"
	"github.com/crossplane-contrib/provider-aws/apis/eks/manualv1alpha1"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	"github.com/tidbcloud/cluster-controller/server/internal/util"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/nodegroup/selfmanaged"
	"github.com/tidbcloud/infra-provider/pkg/scheme"
)

func buildTestNodeGroup(name string, component string, version string, desiredSize int32) *manualv1alpha1.NodeGroup {
	return &manualv1alpha1.NodeGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
			Labels: map[string]string{
				"cluster":   testClusterID,
				"component": component,
			},
		},
		Status: manualv1alpha1.NodeGroupStatus{
			AtProvider: manualv1alpha1.NodeGroupObservation{
				Version: version,
				ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
					DesiredSize: ptr.To(desiredSize),
				},
			},
		},
	}
}

func buildTestASG(name, version string) *v1beta1.AutoScalingGroup {
	return &v1beta1.AutoScalingGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
			Labels: map[string]string{
				"cluster": testClusterID,
			},
			Annotations: map[string]string{
				selfmanaged.K8sVersionAnnotationKey: version,
			},
		},
	}
}

func TestClusterServer_getNodeInfoAndNotes(t *testing.T) {
	tests := []struct {
		name      string
		cluster   *cloudv1.Cluster
		asgs      []*v1beta1.AutoScalingGroup
		ngs       []*manualv1alpha1.NodeGroup
		wantNgs   []*apiv1.NodeGroup
		wantNotes bool
		wantErr   bool
	}{
		{
			name: "tidb nodes use self-managed nodegroups",
			cluster: buildTestClusterCR(testClusterID, func(c *cloudv1.Cluster) {
				c.Annotations = map[string]string{
					consts.UseSelfManagedNodegroupAnnotation: consts.AnnoValTrue,
				}
			}),
			asgs: []*v1beta1.AutoScalingGroup{
				buildTestASG("tidb-asg", "1.29"),
			},
			ngs: []*manualv1alpha1.NodeGroup{
				buildTestNodeGroup("ticdc-ng", "ticdc", "1.29", 3),
				buildTestNodeGroup("dm-ng", "dm", "1.30", 1),
			},
			wantNgs: []*apiv1.NodeGroup{
				{
					Kind:           apiv1.NodeGroup_TIDB_NODES,
					Version:        "1.29",
					MaxSkewVersion: 2,
				},
				{
					Kind:           apiv1.NodeGroup_CDC_NODES,
					Count:          3,
					Version:        "1.29",
					MaxSkewVersion: 2,
				},
				{
					Kind:           apiv1.NodeGroup_DM_NODES,
					Count:          1,
					Version:        "1.30",
					MaxSkewVersion: 2,
				},
			},
		},
		{
			name: "tidb nodes use self-managed nodegroups with different versions",
			cluster: buildTestClusterCR(testClusterID, func(c *cloudv1.Cluster) {
				c.Annotations = map[string]string{
					consts.UseSelfManagedNodegroupAnnotation: consts.AnnoValTrue,
				}
			}),
			asgs: []*v1beta1.AutoScalingGroup{
				buildTestASG("pd-asg", "1.29"),
				buildTestASG("tidb-asg", "1.28"),
				buildTestASG("tikv-asg", "1.27"),
			},
			ngs: []*manualv1alpha1.NodeGroup{
				buildTestNodeGroup("dm-ng", "dm", "1.29", 2),
			},
			wantNgs: []*apiv1.NodeGroup{
				{
					Kind:           apiv1.NodeGroup_TIDB_NODES,
					Version:        "1.27",
					MaxSkewVersion: 2,
				},
				{
					Kind:           apiv1.NodeGroup_DM_NODES,
					Count:          2,
					Version:        "1.29",
					MaxSkewVersion: 2,
				},
			},
			wantNotes: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewWithT(t)

			objs := make([]client.Object, 0, len(tt.ngs)+len(tt.asgs))
			for _, asg := range tt.asgs {
				objs = append(objs, asg)
			}
			for _, ng := range tt.ngs {
				objs = append(objs, ng)
			}
			KubeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(objs...).Build()
			reader := util.LabelSelectorReader{}
			s := ClusterServer{
				KubeClient:    KubeClient,
				clusterReader: &reader,
			}

			gotNgs, gotNotes, err := s.GetNodeInfoAndNotes(context.TODO(), tt.cluster)
			if (err != nil) != tt.wantErr {
				t.Errorf("getNodeInfoAndNotes() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			g.Expect(gotNgs).To(Equal(tt.wantNgs))
			if tt.wantNotes {
				g.Expect(len(gotNotes)).Should(BeNumerically(">", 0))
			} else {
				g.Expect(len(gotNotes)).Should(BeZero())
			}
		})
	}
}
