package v1

import (
	"testing"

	"github.com/stretchr/testify/assert"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestCluster_GetExtraGroup(t *testing.T) {
	type fields struct {
		TypeMeta   metav1.TypeMeta
		ObjectMeta metav1.ObjectMeta
		Spec       ClusterSpec
		Status     ClusterStatus
	}
	tests := []struct {
		name     string
		fields   fields
		groupId  string
		wantSpec *ExtraGroupSpec
		wantOk   bool
	}{
		{
			name: "found existing group",
			fields: fields{
				Spec: ClusterSpec{
					ExtraGroups: []ExtraGroupSpec{
						{
							Name: "group1",
						},
						{
							Name: "group2",
						},
					},
				},
			},
			groupId: "group1",
			wantSpec: &ExtraGroupSpec{
				Name: "group1",
			},
			wantOk: true,
		},
		{
			name: "group not found",
			fields: fields{
				Spec: ClusterSpec{
					ExtraGroups: []ExtraGroupSpec{
						{
							Name: "group1",
						},
					},
				},
			},
			groupId:  "nonexistent",
			wantSpec: nil,
			wantOk:   false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := &Cluster{
				TypeMeta:   tt.fields.TypeMeta,
				ObjectMeta: tt.fields.ObjectMeta,
				Spec:       tt.fields.Spec,
				Status:     tt.fields.Status,
			}
			gotSpec, gotOk := c.GetExtraGroupSpec(tt.groupId)
			assert.Equal(t, tt.wantOk, gotOk)
			assert.Equal(t, tt.wantSpec, gotSpec)

			// Ensure the returned groupSpec is a pointer rather than a copy
			if gotOk {
				gotSpec.SqlEndpoints.DNS.Identity = "abcdefg"
				getAgain, ok := c.GetExtraGroupSpec(gotSpec.Name)
				assert.True(t, ok)
				assert.Equal(t, "abcdefg", getAgain.SqlEndpoints.DNS.Identity)
			}
		})
	}
}

func TestCluster_ExtraGroupSync(t *testing.T) {
	type fields struct {
		TypeMeta   metav1.TypeMeta
		ObjectMeta metav1.ObjectMeta
		Spec       ClusterSpec
		Status     ClusterStatus
	}
	tests := []struct {
		name   string
		fields fields
		want   bool
	}{
		{
			name: "basic",
			fields: fields{
				Spec: ClusterSpec{
					ExtraGroups: []ExtraGroupSpec{
						{
							Name: "test",
							TiDB: TiDB{
								Replicas:     1,
								InstanceType: "c5.xlarge",
							},
						},
					},
				},
				Status: ClusterStatus{
					Phase: ClusterPhaseRunning,
					ExtraEndpoints: []ExtraGroupStatus{
						{
							Name: "test",
							TiDB: &TiDBStatus{
								Status: ComponentNormal,
								Members: []TiDBMember{
									{
										Name:         "test",
										Labels:       map[string]string{},
										Resources:    Resources{},
										Status:       MemberStatusNormal,
										InstanceType: "c5.xlarge",
									},
								},
								PrecheckAudit:          &PrecheckAuditStatus{},
								AuditReady:             false,
								UpdatedReplicas:        1,
								InstanceTypeScalePhase: "",
								Conditions:             []metav1.Condition{},
							},
						},
					},
				},
			},
			want: true,
		},
		{
			name: "zero-replicas",
			fields: fields{
				Spec: ClusterSpec{
					ExtraGroups: []ExtraGroupSpec{
						{
							Name: "test",
							TiDB: TiDB{
								Replicas:     0,
								InstanceType: "",
							},
						},
					},
				},
				Status: ClusterStatus{
					Phase:          ClusterPhaseRunning,
					ExtraEndpoints: []ExtraGroupStatus{},
				},
			},
			want: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := &Cluster{
				TypeMeta:   tt.fields.TypeMeta,
				ObjectMeta: tt.fields.ObjectMeta,
				Spec:       tt.fields.Spec,
				Status:     tt.fields.Status,
			}
			if got := c.ExtraGroupSync(); got != tt.want {
				t.Errorf("Cluster.ExtraGroupSync() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCluster_ExtraGroupSyncAllowsGKEComputeClassMemberInstanceTypes(t *testing.T) {
	cluster := &Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Labels: map[string]string{
				ProviderTypeLabel: ProviderTypeGKE,
			},
		},
		Spec: ClusterSpec{
			ExtraGroups: []ExtraGroupSpec{
				{
					Name: "group1",
					TiDB: TiDB{
						Replicas:     1,
						InstanceType: "n2-standard-8",
					},
				},
			},
		},
		Status: ClusterStatus{
			Phase: ClusterPhaseRunning,
			ExtraEndpoints: []ExtraGroupStatus{
				{
					Name: "group1",
					TiDB: &TiDBStatus{
						Status:                 ComponentNormal,
						Members:                []TiDBMember{{InstanceType: "n2-standard-64", Status: MemberStatusNormal}},
						UpdatedReplicas:        1,
						InstanceTypeScalePhase: InstanceTypeScalePhaseNone,
					},
				},
			},
		},
	}
	updateTestGKEComputeClassBinding(t, cluster, cluster.TiDBGKEComputeClassBindingsAnnoKey("group1"), "n2-standard-8", "group1-base", GKEComputeClassNodePoolBinding{InstanceType: "n2-standard-64", NodePoolName: "group1-extra"})

	assert.True(t, cluster.ExtraGroupSync())
}

func TestCluster_ExtraGroupSyncedReturnsFalseForNilSpec(t *testing.T) {
	cluster := &Cluster{}
	extraGroup := &ExtraGroupCluster{
		Status: &ExtraGroupStatus{},
	}

	assert.False(t, cluster.ExtraGroupSynced(extraGroup))
	assert.False(t, extraGroup.Synced())
}

func TestCluster_GetExtraGroupStatus(t *testing.T) {
	type fields struct {
		TypeMeta   metav1.TypeMeta
		ObjectMeta metav1.ObjectMeta
		Spec       ClusterSpec
		Status     ClusterStatus
	}
	tests := []struct {
		name       string
		fields     fields
		groupId    string
		wantStatus *ExtraGroupStatus
		wantOk     bool
	}{
		{
			name: "found existing group status",
			fields: fields{
				Status: ClusterStatus{
					ExtraEndpoints: []ExtraGroupStatus{
						{
							Name: "group1",
							TiDB: &TiDBStatus{
								Status:          ComponentNormal,
								UpdatedReplicas: 2,
								Members: []TiDBMember{
									{
										Name:         "tidb-0",
										Status:       MemberStatusNormal,
										InstanceType: "c5.xlarge",
									},
								},
							},
							SqlEndpointsStatus: SqlEndpointsStatus{
								PublicEndpoint: &SqlEndpointStatus{
									Host:       "public-host",
									Port:       4000,
									ResourceID: "public-resource-id",
								},
							},
						},
						{
							Name: "group2",
							TiDB: &TiDBStatus{
								Status:          ComponentNormal,
								UpdatedReplicas: 1,
							},
						},
					},
				},
			},
			groupId: "group1",
			wantStatus: &ExtraGroupStatus{
				Name: "group1",
				TiDB: &TiDBStatus{
					Status:          ComponentNormal,
					UpdatedReplicas: 2,
					Members: []TiDBMember{
						{
							Name:         "tidb-0",
							Status:       MemberStatusNormal,
							InstanceType: "c5.xlarge",
						},
					},
				},
				SqlEndpointsStatus: SqlEndpointsStatus{
					PublicEndpoint: &SqlEndpointStatus{
						Host:       "public-host",
						Port:       4000,
						ResourceID: "public-resource-id",
					},
				},
			},
			wantOk: true,
		},
		{
			name: "group status not found",
			fields: fields{
				Status: ClusterStatus{
					ExtraEndpoints: []ExtraGroupStatus{
						{
							Name: "group1",
							TiDB: &TiDBStatus{
								Status:          ComponentNormal,
								UpdatedReplicas: 1,
							},
						},
					},
				},
			},
			groupId:    "nonexistent",
			wantStatus: nil,
			wantOk:     false,
		},
		{
			name: "empty extra endpoints",
			fields: fields{
				Status: ClusterStatus{
					ExtraEndpoints: []ExtraGroupStatus{},
				},
			},
			groupId:    "group1",
			wantStatus: nil,
			wantOk:     false,
		},
		{
			name: "group with TiProxy status",
			fields: fields{
				Status: ClusterStatus{
					ExtraEndpoints: []ExtraGroupStatus{
						{
							Name: "group-with-tiproxy",
							TiDB: &TiDBStatus{
								Status:          ComponentNormal,
								UpdatedReplicas: 1,
							},
							TiProxy: &TiProxyStatus{
								Status:          ComponentNormal,
								UpdatedReplicas: 2,
								Members: []TiProxyMember{
									{
										Name:         "tiproxy-0",
										Status:       MemberStatusNormal,
										InstanceType: "c5.2xlarge",
									},
								},
							},
							SqlEndpointsStatus: SqlEndpointsStatus{
								PublicEndpoint: &SqlEndpointStatus{
									Host:       "public-tiproxy-host",
									Port:       6000,
									ResourceID: "public-tiproxy-resource-id",
								},
								PrivateEndpoint: &SqlEndpointStatus{
									Host:       "private-tiproxy-host",
									Port:       6000,
									ResourceID: "private-tiproxy-resource-id",
								},
							},
						},
					},
				},
			},
			groupId: "group-with-tiproxy",
			wantStatus: &ExtraGroupStatus{
				Name: "group-with-tiproxy",
				TiDB: &TiDBStatus{
					Status:          ComponentNormal,
					UpdatedReplicas: 1,
				},
				TiProxy: &TiProxyStatus{
					Status:          ComponentNormal,
					UpdatedReplicas: 2,
					Members: []TiProxyMember{
						{
							Name:         "tiproxy-0",
							Status:       MemberStatusNormal,
							InstanceType: "c5.2xlarge",
						},
					},
				},
				SqlEndpointsStatus: SqlEndpointsStatus{
					PublicEndpoint: &SqlEndpointStatus{
						Host:       "public-tiproxy-host",
						Port:       6000,
						ResourceID: "public-tiproxy-resource-id",
					},
					PrivateEndpoint: &SqlEndpointStatus{
						Host:       "private-tiproxy-host",
						Port:       6000,
						ResourceID: "private-tiproxy-resource-id",
					},
				},
			},
			wantOk: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := &Cluster{
				TypeMeta:   tt.fields.TypeMeta,
				ObjectMeta: tt.fields.ObjectMeta,
				Spec:       tt.fields.Spec,
				Status:     tt.fields.Status,
			}
			gotStatus, gotOk := c.GetExtraGroupStatus(tt.groupId)
			assert.Equal(t, tt.wantOk, gotOk)
			assert.Equal(t, tt.wantStatus, gotStatus)

			// Ensure the returned status is a pointer rather than a copy
			if gotOk && gotStatus.TiDB != nil {
				originalReplicas := gotStatus.TiDB.UpdatedReplicas
				gotStatus.TiDB.UpdatedReplicas = 999
				getAgain, ok := c.GetExtraGroupStatus(tt.groupId)
				assert.True(t, ok)
				assert.Equal(t, int32(999), getAgain.TiDB.UpdatedReplicas)
				// Restore original value for clean test
				gotStatus.TiDB.UpdatedReplicas = originalReplicas
			}
		})
	}
}

func TestCluster_ExtraGroupGetUpToDateReplicas(t *testing.T) {
	type fields struct {
		TypeMeta   metav1.TypeMeta
		ObjectMeta metav1.ObjectMeta
		Spec       ClusterSpec
		Status     ClusterStatus
	}
	type args struct {
		comp ComponentType
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		setup  func(*Cluster)
		want   int
	}{
		{
			name: "basic",
			fields: fields{
				Spec: ClusterSpec{
					ExtraGroups: []ExtraGroupSpec{
						{
							Name: "test",
							TiDB: TiDB{
								Replicas:      1,
								InstanceType:  "c5.xlarge",
								Audit:         &Audit{},
								PrecheckAudit: &Audit{},
								Security:      &TiDBSecurity{},
								SuspendAction: &SuspendAction{},
							},
						},
					},
				},
				Status: ClusterStatus{
					ExtraEndpoints: []ExtraGroupStatus{
						{
							Name: "test",
							TiDB: &TiDBStatus{
								Status: "",
								Members: []TiDBMember{
									{
										Name:         "test",
										Labels:       map[string]string{},
										Resources:    Resources{},
										Status:       MemberStatusNormal,
										InstanceType: "c5.xlarge",
									},
								},
								PrecheckAudit:          &PrecheckAuditStatus{},
								AuditReady:             false,
								UpdatedReplicas:        1,
								InstanceTypeScalePhase: "",
								Conditions:             []metav1.Condition{},
							},
						},
					},
				},
			},
			args: args{
				comp: "tidb",
			},
			want: 1,
		},
		{
			name: "zero replicas for initialization",
			fields: fields{
				Spec: ClusterSpec{
					ExtraGroups: []ExtraGroupSpec{
						{
							Name: "11",
							TiDB: TiDB{
								Replicas:     0,
								InstanceType: "",
							},
						},
					},
				},
				Status: ClusterStatus{
					ExtraEndpoints: []ExtraGroupStatus{},
				},
			},
			args: args{
				comp: "tidb",
			},
			want: 0,
		},
		{
			name: "multiple extraGroup replicas",
			fields: fields{
				Spec: ClusterSpec{
					ExtraGroups: []ExtraGroupSpec{
						{
							Name: "t1",
							TiDB: TiDB{
								Replicas:      1,
								InstanceType:  "c5.xlarge",
								Audit:         &Audit{},
								PrecheckAudit: &Audit{},
								Security:      &TiDBSecurity{},
								SuspendAction: &SuspendAction{},
							},
						},
						{
							Name: "t2",
							TiDB: TiDB{
								Replicas:      1,
								InstanceType:  "c5.xlarge",
								Audit:         &Audit{},
								PrecheckAudit: &Audit{},
								Security:      &TiDBSecurity{},
								SuspendAction: &SuspendAction{},
							},
						},
					},
				},
				Status: ClusterStatus{
					ExtraEndpoints: []ExtraGroupStatus{
						{
							Name: "t1",
							TiDB: &TiDBStatus{
								Status: "",
								Members: []TiDBMember{
									{
										Name:         "test",
										Labels:       map[string]string{},
										Resources:    Resources{},
										Status:       MemberStatusNormal,
										InstanceType: "c5.xlarge",
									},
								},
								PrecheckAudit:          &PrecheckAuditStatus{},
								AuditReady:             false,
								UpdatedReplicas:        1,
								InstanceTypeScalePhase: "",
								Conditions:             []metav1.Condition{},
							},
						},
						{
							Name: "t2",
							TiDB: &TiDBStatus{
								Status: "",
								Members: []TiDBMember{
									{
										Name:         "test",
										Labels:       map[string]string{},
										Resources:    Resources{},
										Status:       MemberStatusNormal,
										InstanceType: "c5.xlarge",
									},
								},
								PrecheckAudit:          &PrecheckAuditStatus{},
								AuditReady:             false,
								UpdatedReplicas:        1,
								InstanceTypeScalePhase: "",
								Conditions:             []metav1.Condition{},
							},
						},
					},
				},
			},
			args: args{
				comp: "tidb",
			},
			want: 2,
		},
		{
			name: "heterogeneous extraGroup replicas allowed",
			fields: fields{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						ProviderTypeLabel: ProviderTypeGKE,
					},
				},
				Spec: ClusterSpec{
					ExtraGroups: []ExtraGroupSpec{
						{
							Name: "group1",
							TiDB: TiDB{
								Replicas:      2,
								InstanceType:  "c5.xlarge",
								Audit:         &Audit{},
								PrecheckAudit: &Audit{},
								Security:      &TiDBSecurity{},
								SuspendAction: &SuspendAction{},
							},
						},
					},
				},
				Status: ClusterStatus{
					ExtraEndpoints: []ExtraGroupStatus{
						{
							Name: "group1",
							TiDB: &TiDBStatus{
								Status: ComponentNormal,
								Members: []TiDBMember{
									{
										Name:         "tidb-0",
										Labels:       map[string]string{},
										Resources:    Resources{},
										Status:       MemberStatusNormal,
										InstanceType: "n2-standard-64",
									},
									{
										Name:         "tidb-1",
										Labels:       map[string]string{},
										Resources:    Resources{},
										Status:       MemberStatusNormal,
										InstanceType: "n2-standard-64",
									},
								},
								UpdatedReplicas:        2,
								InstanceTypeScalePhase: InstanceTypeScalePhaseNone,
								Conditions:             []metav1.Condition{},
							},
						},
					},
				},
			},
			args: args{
				comp: "tidb",
			},
			setup: func(c *Cluster) {
				updateTestGKEComputeClassBinding(t, c, c.TiDBGKEComputeClassBindingsAnnoKey("group1"), "c5.xlarge", "group1-base", GKEComputeClassNodePoolBinding{InstanceType: "n2-standard-64", NodePoolName: "group1-extra"})
			},
			want: 2,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := &Cluster{
				TypeMeta:   tt.fields.TypeMeta,
				ObjectMeta: tt.fields.ObjectMeta,
				Spec:       tt.fields.Spec,
				Status:     tt.fields.Status,
			}
			if tt.setup != nil {
				tt.setup(c)
			}
			if got := c.ExtraGroupGetUpToDateReplicas(tt.args.comp); got != tt.want {
				t.Errorf("Cluster.ExtraGroupGetUpToDateReplicas() = %v, want %v", got, tt.want)
			}
		})
	}
}
