package common

import (
	"encoding/json"
	"testing"

	opv2 "github.com/pingcap/tidb-operator/api/v2/core/v1alpha1"
	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/eks-provider/controllers/cluster/task/common/feature"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

func Test_buildSchedulePolicies(t *testing.T) {
	tests := []struct {
		name  string
		zones []string
		want  []opv2.SchedulePolicy
	}{
		{
			name: "eks",
			zones: []string{
				"us-west-2a",
				"us-west-2b",
				"us-west-2c",
			},
			want: []opv2.SchedulePolicy{
				{
					Type: opv2.SchedulePolicyTypeEvenlySpread,
					EvenlySpread: &opv2.SchedulePolicyEvenlySpread{
						Topologies: []opv2.ScheduleTopology{
							{Topology: opv2.Topology{consts.ZoneLabelKey: "us-west-2a"}},
							{Topology: opv2.Topology{consts.ZoneLabelKey: "us-west-2b"}},
							{Topology: opv2.Topology{consts.ZoneLabelKey: "us-west-2c"}},
						},
					},
				},
			},
		},
		{
			name:  "eks",
			zones: []string{},
			want: []opv2.SchedulePolicy{
				{
					Type: opv2.SchedulePolicyTypeEvenlySpread,
					EvenlySpread: &opv2.SchedulePolicyEvenlySpread{
						Topologies: []opv2.ScheduleTopology{},
					},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equalf(t, tt.want, buildSchedulePolicies(tt.zones), "buildSchedulePolicies(%v)", tt.zones)
		})
	}
}

func TestTiDBClusterV2ExpectedClusterNormalizeLastAppliedAnnotation(t *testing.T) {
	tests := []struct {
		name         string
		featureGates map[config.FeatureGateName]config.FeatureGate
		wantMarker   bool
	}{
		{
			name:       "feature gate disabled by default",
			wantMarker: false,
		},
		{
			name: "feature gate enabled for project",
			featureGates: map[config.FeatureGateName]config.FeatureGate{
				config.NormalizeLastAppliedConfiguration: {
					EnabledProjects: []config.UintOrString{"project-1"},
				},
			},
			wantMarker: true,
		},
		{
			name: "feature gate does not match project",
			featureGates: map[config.FeatureGateName]config.FeatureGate{
				config.NormalizeLastAppliedConfiguration: {
					EnabledProjects: []config.UintOrString{"project-2"},
				},
			},
			wantMarker: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tcTask := &TiDBClusterV2{
				BaseTask: &task.BaseTask{
					Context: &task.Context{
						Cluster: &cloudv1.Cluster{
							ObjectMeta: metav1.ObjectMeta{
								Name: "test-cluster",
								Labels: map[string]string{
									cloudv1.ProjectTagKey: "project-1",
								},
							},
						},
						Namer:  namer.NewNamer().WithClusterName("test-cluster"),
						Config: config.RootConfig{ControllerConfig: config.ControllerConfig{FeatureGates: tt.featureGates}},
					},
				},
				configRevisionName: "config-revision-1",
				featureGates:       feature.NewGates(),
			}

			cluster := tcTask.expectedCluster()
			_, ok := cluster.Annotations[consts.NormalizeLastAppliedConfigAnnotation]
			assert.Equal(t, tt.wantMarker, ok)
			assert.Equal(t, "config-revision-1", cluster.Annotations[cloudv1.ConfigRevisionIdKey])
		})
	}
}

func TestNormalizeLastAppliedConfigAnnotationForCreateOrUpdate(t *testing.T) {
	obj := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
		consts.LastAppliedConfigAnnotation: `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"test","namespace":"default","uid":"test-uid","generation":10,"creationTimestamp":"2026-06-15T00:00:00Z","managedFields":[{"manager":"test"}],"annotations":{"tidbcloud.com/last-applied-configuration":"recursive","tidbcloud.com/normalize-last-applied-configuration":"true"}},"data":{"foo":"bar"},"status":{"observed":"dynamic"}}`,
	}}}

	normalizeLastAppliedConfigAnnotationForCreateOrUpdate(obj, false)
	var raw map[string]any
	assert.NoError(t, json.Unmarshal([]byte(obj.Annotations[consts.LastAppliedConfigAnnotation]), &raw))
	_, ok := raw["status"]
	assert.True(t, ok)

	normalizeLastAppliedConfigAnnotationForCreateOrUpdate(obj, true)
	var normalized map[string]any
	assert.NoError(t, json.Unmarshal([]byte(obj.Annotations[consts.LastAppliedConfigAnnotation]), &normalized))
	_, ok = normalized["status"]
	assert.False(t, ok)

	metadata := normalized["metadata"].(map[string]any)
	for _, field := range []string{"uid", "generation", "creationTimestamp", "managedFields"} {
		_, ok = metadata[field]
		assert.False(t, ok, field)
	}
	annotations := metadata["annotations"].(map[string]any)
	_, ok = annotations[consts.LastAppliedConfigAnnotation]
	assert.False(t, ok)
	assert.Equal(t, "true", annotations[consts.NormalizeLastAppliedConfigAnnotation])
}

func TestReconcileNormalizeLastAppliedConfigAnnotation(t *testing.T) {
	tests := []struct {
		name       string
		obj        *corev1.ConfigMap
		enabled    bool
		wantMarker bool
	}{
		{
			name:       "enabled sets marker when annotations are nil",
			obj:        &corev1.ConfigMap{},
			enabled:    true,
			wantMarker: true,
		},
		{
			name: "disabled removes existing marker and keeps other annotations",
			obj: &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
				consts.NormalizeLastAppliedConfigAnnotation: "true",
				"third-party": "keep",
			}}},
			enabled:    false,
			wantMarker: false,
		},
		{
			name:       "disabled is noop when annotations are nil",
			obj:        &corev1.ConfigMap{},
			enabled:    false,
			wantMarker: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reconcileNormalizeLastAppliedConfigAnnotation(tt.obj, tt.enabled)
			_, ok := tt.obj.Annotations[consts.NormalizeLastAppliedConfigAnnotation]
			assert.Equal(t, tt.wantMarker, ok)
			if _, ok := tt.obj.Annotations["third-party"]; ok {
				assert.Equal(t, "keep", tt.obj.Annotations["third-party"])
			}
		})
	}
}
