// Copyright 2026 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package volumes

import (
	"context"
	"testing"

	. "github.com/onsi/gomega"
	"github.com/pingcap/tidb-operator/pkg/apis/label"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/pingcap/tidb-operator/pkg/controller"
	apps "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kubefake "k8s.io/client-go/kubernetes/fake"
)

func TestTryToRecreateSTSPreservesConfigMapOnlyForVolumeChange(t *testing.T) {
	const configMapNameBeforeDeleteAnnotation = "tidb.pingcap.com/configmap-name-before-delete-tikv"

	tests := []struct {
		name             string
		desiredSize      string
		expectSTSDeleted bool
	}{
		{
			name:             "volume is unchanged",
			desiredSize:      "10Gi",
			expectSTSDeleted: false,
		},
		{
			name:             "volume is expanded",
			desiredSize:      "20Gi",
			expectSTSDeleted: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			tc := &v1alpha1.TidbCluster{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "demo",
					Namespace: corev1.NamespaceDefault,
				},
			}
			tc.Status.TiKV.Phase = v1alpha1.NormalPhase

			replicas := int32(3)
			inUseConfigMap := "demo-tikv-existing"
			sts := &apps.StatefulSet{
				ObjectMeta: metav1.ObjectMeta{
					Name:      controller.TiKVMemberName(tc.Name),
					Namespace: tc.Namespace,
					Labels: map[string]string{
						label.ComponentLabelKey: string(v1alpha1.TiKVMemberType),
					},
				},
				Spec: apps.StatefulSetSpec{
					Replicas: &replicas,
					Template: corev1.PodTemplateSpec{
						Spec: corev1.PodSpec{
							Volumes: []corev1.Volume{
								{
									Name: "config",
									VolumeSource: corev1.VolumeSource{
										ConfigMap: &corev1.ConfigMapVolumeSource{
											LocalObjectReference: corev1.LocalObjectReference{Name: inUseConfigMap},
										},
									},
								},
							},
						},
					},
					VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
						{
							ObjectMeta: metav1.ObjectMeta{Name: string(v1alpha1.GetStorageVolumeName("", v1alpha1.TiKVMemberType))},
							Spec: corev1.PersistentVolumeClaimSpec{
								Resources: corev1.ResourceRequirements{
									Requests: corev1.ResourceList{corev1.ResourceStorage: resource.MustParse("10Gi")},
								},
							},
						},
					},
				},
				Status: apps.StatefulSetStatus{
					CurrentRevision: "revision",
					UpdateRevision:  "revision",
				},
			}

			deps := controller.NewFakeDependencies()
			kubeClient := kubefake.NewSimpleClientset(sts)
			deps.KubeClientset = kubeClient
			g.Expect(deps.TiDBClusterControl.Create(tc)).To(Succeed())

			modifier := &pvcModifier{deps: deps}
			ctx := &componentVolumeContext{
				Context: context.Background(),
				tc:      tc,
				status:  &tc.Status.TiKV,
				sts:     sts,
				desiredVolumes: []DesiredVolume{
					{
						Name: v1alpha1.GetStorageVolumeName("", v1alpha1.TiKVMemberType),
						Size: resource.MustParse(tt.desiredSize),
					},
				},
			}

			g.Expect(modifier.tryToRecreateSTS(ctx)).To(Succeed())

			deleteCount := 0
			for _, action := range kubeClient.Actions() {
				if action.GetVerb() == "delete" && action.GetResource().Resource == "statefulsets" {
					deleteCount++
				}
			}
			if tt.expectSTSDeleted {
				g.Expect(deleteCount).To(Equal(1))
				g.Expect(tc.Annotations).To(HaveKeyWithValue(configMapNameBeforeDeleteAnnotation, inUseConfigMap))
			} else {
				g.Expect(deleteCount).To(Equal(0))
				g.Expect(tc.Annotations).NotTo(HaveKey(configMapNameBeforeDeleteAnnotation))
			}
		})
	}
}

func TestModifyVolumesDoesNotRequireTiKVStoreUpWhenVolumeIsUnchanged(t *testing.T) {
	g := NewGomegaWithT(t)
	tc := &v1alpha1.TidbCluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "demo",
			Namespace: corev1.NamespaceDefault,
		},
	}
	tc.Status.TiKV.Phase = v1alpha1.NormalPhase
	podName := controller.TiKVMemberName(tc.Name) + "-0"
	tc.Status.TiKV.Stores = map[string]v1alpha1.TiKVStore{
		"1": {
			PodName: podName,
			State:   v1alpha1.TiKVStateDown,
		},
	}

	replicas := int32(1)
	sts := &apps.StatefulSet{
		ObjectMeta: metav1.ObjectMeta{
			Name:      controller.TiKVMemberName(tc.Name),
			Namespace: tc.Namespace,
		},
		Spec: apps.StatefulSetSpec{Replicas: &replicas},
		Status: apps.StatefulSetStatus{
			CurrentRevision: "revision",
			UpdateRevision:  "revision",
		},
	}
	pod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      podName,
			Namespace: tc.Namespace,
			Labels:    label.New().Instance(tc.GetInstanceName()).TiKV(),
		},
	}

	deps := controller.NewFakeDependencies()
	g.Expect(deps.KubeInformerFactory.Apps().V1().StatefulSets().Informer().GetIndexer().Add(sts)).To(Succeed())
	g.Expect(deps.KubeInformerFactory.Core().V1().Pods().Informer().GetIndexer().Add(pod)).To(Succeed())
	modifier := &pvcModifier{
		deps: deps,
		sf:   MustNewSelectorFactory(),
		pm:   &FakePodVolumeModifier{},
	}

	ctx, err := modifier.buildContextForTC(tc, &tc.Status.TiKV)
	g.Expect(err).To(Succeed())
	g.Expect(modifier.modifyVolumes(ctx)).To(Succeed())
}
