package tidbreclaimer

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/go-logr/logr"
	. "github.com/onsi/gomega"
	"github.com/tidbcloud/aws-shared-provider/pkg/scheme"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils/clientfake"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils/tidb"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/log"
)

const (
	standbyData = `{"state":"standby","keyspace_name":""}`
	activeData  = `{"state":"activated","keyspace_name":"dummy"}`
)

func TestTidbReclaimer_ReclaimPods(t *testing.T) {
	type args []string
	tidbPoolNamespace := "tidb-pool"

	var i int
	var data []string
	// use one server for all pods, so string array is used to respond to different requests
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, data[i])
		i++
	}))
	tsHost := ts.Listener.Addr().(*net.TCPAddr)
	tsIP := tsHost.IP.String()
	tsPort := tsHost.Port
	defer ts.Close()

	tests := []struct {
		name      string
		args      args
		pods      []runtime.Object
		freeCache map[string]string
		podstats  []string
		expectRes int
	}{
		{
			name: "reclaim pod1 succeed",
			args: args{"pod1"},
			pods: []runtime.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			freeCache: map[string]string{},
			podstats:  []string{standbyData},
			expectRes: 1,
		}, {
			name: "reclaim pod1 with unready status succeed",
			args: args{"pod1"},
			pods: []runtime.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionFalse}},
					},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			freeCache: map[string]string{},
			podstats:  []string{standbyData},
			expectRes: 1,
		}, {
			name: "reclaim pod1 with activated status, not succeed",
			args: args{"pod1"},
			pods: []runtime.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			freeCache: map[string]string{},
			podstats:  []string{activeData},
			expectRes: 0,
		}, {
			name: "reclaim pod1 not found in k8s, not succeed",
			args: args{"pod1"},
			pods: []runtime.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod3", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			freeCache: map[string]string{},
			podstats:  []string{standbyData},
			expectRes: 0,
		}, {
			name: "reclaim pod1 in freeCache, not succeed",
			args: args{"pod1"},
			pods: []runtime.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			freeCache: map[string]string{"pod1": tsIP},
			podstats:  []string{standbyData},
			expectRes: 0,
		}, {
			name: "reclaim pod1 and pod2, succeed",
			args: args{"pod1", "pod2"},
			pods: []runtime.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			freeCache: map[string]string{},
			podstats:  []string{standbyData, standbyData},
			expectRes: 2,
		}, {
			name: "reclaim pod1 succeed, pod2 not succeed",
			args: args{"pod1", "pod2"},
			pods: []runtime.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      tsIP,
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			freeCache: map[string]string{},
			podstats:  []string{standbyData, activeData},
			expectRes: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			g.Expect(len(tt.args)).To(Equal(len(tt.podstats)))
			tm := &clientfake.FakeTidbManager{
				FreeCacheR: tt.freeCache,
			}
			tr := &TidbReclaimer{
				Logger:            logr.New(log.NullLogSink{}),
				ctx:               context.Background(),
				client:            fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(tt.pods...).Build(),
				tManager:          tm,
				tidbController:    tidb.NewController(nil, ""),
				tidbPoolNamespace: tidbPoolNamespace,
			}

			i = 0
			data = tt.podstats
			cnt := tr.ReclaimPods(tt.args, tsPort)
			g.Expect(cnt).To(Equal(tt.expectRes))
		})
	}
}
