package pod

import (
	"context"
	"testing"
	"time"

	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/pkg/pingcap/label"
	"github.com/tidbcloud/shared-provider/pkg/scheme"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
	evictpod "github.com/tidbcloud/shared-provider/pkg/utils/pod"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	apitypes "k8s.io/apimachinery/pkg/types"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func timeToPtr(t metav1.Time) *metav1.Time {
	return &t
}

func TestReconcile(t *testing.T) {
	cases := []struct {
		name string

		pod     *corev1.Pod
		cluster *cloudv1.Cluster

		safeToEvict bool
	}{
		{
			name: "DelSafeToEvictAnno",
			pod: &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "tidb",
					Namespace: "test-user-ns",
					Annotations: map[string]string{
						evictpod.SafeToEvictAnno: "false",
					},
					Labels: map[string]string{
						label.ComponentLabelKey:  "tidb",
						labels.LabelKeyName:      "test-cluster",
						labels.LabelKeyNamespace: "test-cluster-ns",
					},
				},
			},
			cluster: &cloudv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cluster",
					Namespace: "test-cluster-ns",
					Labels: map[string]string{
						labels.LabelKeyKubernetesCluster: "dev-us-east-1-s01",
					},
				},
				Status: cloudv1.ClusterStatus{
					LastActiveTimestamp: timeToPtr(metav1.NewTime(time.Now().Add(-30 * time.Minute))),
				},
			},
			safeToEvict: false,
		},
		{
			name: "AddSafeToEvictAnno",
			pod: &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "pd",
					Namespace: "test-user-ns",
					Labels: map[string]string{
						label.ComponentLabelKey:  "pd",
						labels.LabelKeyName:      "test-cluster",
						labels.LabelKeyNamespace: "test-cluster-ns",
					},
				},
			},
			cluster: &cloudv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cluster",
					Namespace: "test-cluster-ns",
					Labels: map[string]string{
						labels.LabelKeyKubernetesCluster: "dev-us-east-1-s01",
					},
				},
				Status: cloudv1.ClusterStatus{
					LastActiveTimestamp: timeToPtr(metav1.Now()),
				},
			},
			safeToEvict: true,
		},
		{
			name: "discovery",
			pod: &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "discovery",
					Namespace: "test-user-ns",
					Labels: map[string]string{
						label.ComponentLabelKey:  "discovery",
						labels.LabelKeyName:      "test-cluster",
						labels.LabelKeyNamespace: "test-cluster-ns",
					},
				},
			},
			cluster: &cloudv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cluster",
					Namespace: "test-cluster-ns",
					Labels: map[string]string{
						labels.LabelKeyKubernetesCluster: "dev-us-east-1-s01",
					},
				},
				Status: cloudv1.ClusterStatus{
					LastActiveTimestamp: timeToPtr(metav1.Now()),
				},
			},
			safeToEvict: false,
		},
		{
			name: "notOurCluster",
			pod: &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "tidb",
					Namespace: "test-user-ns",
					Labels: map[string]string{
						label.ComponentLabelKey:  "tidb",
						labels.LabelKeyName:      "test-cluster",
						labels.LabelKeyNamespace: "test-cluster-ns",
					},
				},
			},
			cluster: &cloudv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cluster",
					Namespace: "test-cluster-ns",
					Labels: map[string]string{
						labels.LabelKeyKubernetesCluster: "notOurCluster",
					},
				},
				Status: cloudv1.ClusterStatus{
					LastActiveTimestamp: timeToPtr(metav1.Now()),
				},
			},
			safeToEvict: false,
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			r := newFakeReconciler(c.pod, c.cluster)
			req := ctrl.Request{
				NamespacedName: apitypes.NamespacedName{
					Namespace: c.pod.Namespace,
					Name:      c.pod.Name,
				},
			}
			_, err := r.Reconcile(context.Background(), req)
			assert.NoError(t, err)

			pod := &corev1.Pod{}
			err = r.Client.Get(context.Background(), req.NamespacedName, pod)
			assert.NoError(t, err)
			_, ok := pod.Annotations[evictpod.SafeToEvictAnno]
			assert.Equal(t, c.safeToEvict, ok)
		})
	}
}

func newFakeReconciler(obj, infraObj runtime.Object) *Reconciler {
	fakeClient := fake.NewClientBuilder().
		WithScheme(scheme.Scheme).
		WithRuntimeObjects(obj).
		Build()
	fakeInfraClient := fake.NewClientBuilder().
		WithScheme(scheme.Scheme).
		WithRuntimeObjects(infraObj).
		Build()

	return &Reconciler{
		Client:          fakeClient,
		Cache:           fakeClient,
		InfraCache:      fakeInfraClient,
		KubeClusterName: "dev-us-east-1-s01",
		EvictDuration:   time.Second,
		Logger:          logr.Discard(),
	}
}
