package tidbmanager

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

	"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"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils/clientfake"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils/tidb"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/log"
)

func Test_tidbManager_CheckTidb(t1 *testing.T) {
	var data string
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, data)
	}))
	tsHost := ts.Listener.Addr().(*net.TCPAddr)
	tsIP := tsHost.IP.String()
	tidbStatusPort = tsHost.Port // set tidbStatusPort to ts port
	defer ts.Close()

	tidbPoolNamespace := "tidb-pool"
	type args struct {
		token string
		podIP string
	}
	tests := []struct {
		name          string
		args          args
		pods          []runtime.Object
		tidbStatus    string
		assignCache   map[string]tidbInfo
		wantRemovePod []string
		wantErr       error
	}{
		{
			name:    "CheckTidb, not found token",
			args:    args{token: "token1", podIP: tsIP},
			wantErr: utils.ErrNotFoundToken,
		},
		{
			name: "CheckTidb, not found in k8s",
			args: args{token: "token1", podIP: tsIP},
			pods: []runtime.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: tidbPoolNamespace},
					Status: corev1.PodStatus{
						Phase:      corev1.PodRunning,
						PodIP:      "ip2",
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			assignCache: map[string]tidbInfo{
				"token1": {endpoints: []string{"pod1," + tsIP, "pod2,ip2"}},
			},
		},
		{
			name: "CheckTidb, remove not ready pod",
			args: args{token: "token1", podIP: tsIP},
			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:      "ip2",
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			assignCache: map[string]tidbInfo{
				"token1": {endpoints: []string{"pod1," + tsIP, "pod2,ip2"}},
			},
			wantRemovePod: []string{"pod1"},
		},
		{
			name: "CheckTidb, remove standby endpoint",
			args: args{token: "token1", podIP: tsIP},
			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:      "ip2",
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			tidbStatus: `{"state":"standby","keyspace_name":""}`,
			assignCache: map[string]tidbInfo{
				"token1": {endpoints: []string{"pod1," + tsIP, "pod2,ip2"}},
			},
		},
		{
			name: "CheckTidb, remove other token endpoint",
			args: args{token: "token1", podIP: tsIP},
			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:      "ip2",
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			tidbStatus: `{"state":"activated","keyspace_name":"token_other"}`,
			assignCache: map[string]tidbInfo{
				"token1": {endpoints: []string{"pod1," + tsIP, "pod2,ip2"}},
			},
		},
		{
			name: "CheckTidb, remove nothing",
			args: args{token: "token1", podIP: tsIP},
			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:      "ip2",
						Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}},
					},
				},
			},
			tidbStatus: `{"state":"activated","keyspace_name":"token1"}`,
			assignCache: map[string]tidbInfo{
				"token1": {endpoints: []string{"pod1," + tsIP, "pod2,ip2"}},
			},
		},
	}
	for _, tt := range tests {
		t1.Run(tt.name, func(t1 *testing.T) {
			g := NewGomegaWithT(t1)
			ch := make(chan struct{}, 1)
			t := &tidbManager{
				Logger:            logr.New(log.NullLogSink{}),
				ctx:               context.Background(),
				tidbPoolNamespace: tidbPoolNamespace,
				client:            fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(tt.pods...).Build(),
				etcdKV:            clientfake.NewEtcdKV(clientfake.BuildArg{}),
				etcdLease:         clientfake.NewEtcdLease(),
				tidbController:    tidb.NewController(nil, ""),
				assignCache:       &assignCache{},
				removePodQueue:    make(chan removePod, 10),
				elected:           ch,
			}
			t.assignCache.init(tt.assignCache)
			data = tt.tidbStatus

			err := t.CheckTidb(context.Background(), tt.args.token, tt.args.podIP)
			if tt.wantErr == nil {
				g.Expect(err).To(Succeed())
			} else {
				g.Expect(err).To(MatchError(tt.wantErr))
			}
			if len(tt.wantRemovePod) > 0 {
				ch <- struct{}{}
				t.processRemovePod(1)
				time.Sleep(1 * time.Second) // wait processRemovePod start worker

				for _, name := range tt.wantRemovePod {
					err := t.client.Get(context.Background(), client.ObjectKey{
						Name:      name,
						Namespace: tidbPoolNamespace,
					}, &corev1.Pod{})
					g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
				}
			}
		})
	}
}
