package network

import (
	"context"
	"testing"

	"github.com/go-logr/logr"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/stretchr/testify/assert"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	awsfake "github.com/tidbcloud/infra-api/pkg/providers/aws/fake"
	"github.com/tidbcloud/shared-provider/pkg/scheme"
	fake "github.com/tidbcloud/shared-provider/pkg/testing"
	"github.com/tidbcloud/shared-provider/pkg/types"
	"github.com/tidbcloud/shared-provider/pkg/utils/template"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	kubetesting "k8s.io/client-go/testing"
	"k8s.io/client-go/tools/record"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func fakeReconciler(client, infraClient client.Client) *Reconciler {
	fakeRecorder := record.NewFakeRecorder(recorderBufSize)
	fakeInfraRecorder := record.NewFakeRecorder(recorderBufSize)

	return &Reconciler{
		Client:                  client,
		InfraClient:             infraClient,
		Cache:                   client,
		InfraCache:              infraClient,
		Recorder:                fakeRecorder,
		InfraRecorder:           fakeInfraRecorder,
		AwsClient:               nil,
		KubeClusterName:         kubeClusterName,
		AWSAccountID:            "123456",
		OIDCProviderARN:         "fedcba",
		AddonDir:                "",
		AddonConfigMapNamespace: addonConfigMapNamespace,
		AddonConfigMapName:      addonConfigMapName,
		Logger:                  logr.Discard(),
		tmpl:                    template.New(scheme.Scheme),
		tracker:                 NewTracker(),
	}
}

func TestRescheduleAws(t *testing.T) {
	cases := []struct {
		desc string
		net  *cloudv1.Network

		expectedRescheduled  bool
		expectedNetwork      *cloudv1.Network
		expectedActions      []kubetesting.Action
		expectedInfraActions []kubetesting.Action
	}{
		{
			desc: "no annotation",
			net: &cloudv1.Network{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "aaa",
					Namespace: "aaa",
				},
				Spec: cloudv1.NetworkSpec{Provider: types.ProviderTypeAWSSharedTier},
			},
			expectedRescheduled: false,
			expectedNetwork: &cloudv1.Network{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "aaa",
					Namespace: "aaa",
				},
				Spec: cloudv1.NetworkSpec{Provider: types.ProviderTypeAWSSharedTier},
			},
			expectedActions:      []kubetesting.Action{},
			expectedInfraActions: []kubetesting.Action{},
		},
		{
			desc: "annotation reschedule is not set",
			net: &cloudv1.Network{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "aaa",
					Namespace: "aaa",
					Annotations: map[string]string{
						"aaa": "bbb",
					},
				},
				Spec: cloudv1.NetworkSpec{Provider: types.ProviderTypeAWSSharedTier},
			},
			expectedRescheduled: false,
			expectedNetwork: &cloudv1.Network{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "aaa",
					Namespace: "aaa",
					Annotations: map[string]string{
						"aaa": "bbb",
					},
				},
				Spec: cloudv1.NetworkSpec{Provider: types.ProviderTypeAWSSharedTier},
			},
			expectedActions:      []kubetesting.Action{},
			expectedInfraActions: []kubetesting.Action{},
		},
		{
			desc: "annotation reschedule is set",
			net: &cloudv1.Network{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "aaa",
					Namespace: "aaa",
					Annotations: map[string]string{
						types.AnnoKeyReschedule: "fake",
					},
					Labels: map[string]string{
						"tags.tidbcloud.com/kubernetes": kubeClusterName,
					},
				},
				Spec: cloudv1.NetworkSpec{Provider: types.ProviderTypeAWSSharedTier},
			},
			expectedRescheduled: true,
			expectedNetwork: &cloudv1.Network{
				ObjectMeta: metav1.ObjectMeta{
					Name:        "aaa",
					Namespace:   "aaa",
					Annotations: map[string]string{},
					Labels: map[string]string{
						"tags.tidbcloud.com/kubernetes": "fake",
					},
					ResourceVersion: "1",
				},
				Spec: cloudv1.NetworkSpec{Provider: types.ProviderTypeAWSSharedTier},
			},
			expectedActions: []kubetesting.Action{
				kubetesting.NewRootGetAction(schema.GroupVersionResource{
					Group:    "",
					Version:  "v1",
					Resource: "namespaces",
				}, "db-aaa"),
				kubetesting.NewDeleteAction(schema.GroupVersionResource{
					Group:    "",
					Version:  "v1",
					Resource: "serviceaccounts",
				}, "db-aaa", "aaa-import"),
			},
			expectedInfraActions: []kubetesting.Action{
				kubetesting.NewUpdateAction(
					cloudv1.GroupVersion.WithResource("networks"),
					"aaa",
					&cloudv1.Network{
						ObjectMeta: metav1.ObjectMeta{
							Name:        "aaa",
							Namespace:   "aaa",
							Annotations: map[string]string{},
							Labels: map[string]string{
								"tags.tidbcloud.com/kubernetes": "fake",
							},
						},
						Spec: cloudv1.NetworkSpec{Provider: types.ProviderTypeAWSSharedTier},
					},
				),
			},
		},
		{
			desc: "abort reschedule because cluster exists",
			net: &cloudv1.Network{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "bbb",
					Namespace: "bbb",
					Annotations: map[string]string{
						types.AnnoKeyReschedule: "fake",
					},
					Labels: map[string]string{
						"tags.tidbcloud.com/kubernetes": kubeClusterName,
					},
				},
				Spec: cloudv1.NetworkSpec{Provider: types.ProviderTypeAWSSharedTier},
			},
			expectedRescheduled: false,
			expectedNetwork: &cloudv1.Network{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "bbb",
					Namespace: "bbb",
					Annotations: map[string]string{
						types.AnnoKeyRescheduleFailedReason: reasonTiDBClusterExists,
					},
					Labels: map[string]string{
						"tags.tidbcloud.com/kubernetes": kubeClusterName,
					},
					ResourceVersion: "1",
				},
				Spec: cloudv1.NetworkSpec{Provider: types.ProviderTypeAWSSharedTier},
			},
			expectedActions: []kubetesting.Action{
				kubetesting.NewRootGetAction(schema.GroupVersionResource{
					Group:    "",
					Version:  "v1",
					Resource: "namespaces",
				}, "db-bbb"),
				kubetesting.NewListAction(
					v1alpha1.SchemeGroupVersion.WithResource("tidbclusters"),
					v1alpha1.SchemeGroupVersion.WithKind("TidbCluster"),
					"db-bbb",
					metav1.ListOptions{},
				),
			},
			expectedInfraActions: []kubetesting.Action{
				kubetesting.NewUpdateAction(
					cloudv1.GroupVersion.WithResource("networks"),
					"bbb",
					&cloudv1.Network{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "bbb",
							Namespace: "bbb",
							Annotations: map[string]string{
								types.AnnoKeyRescheduleFailedReason: reasonTiDBClusterExists,
							},
							Labels: map[string]string{
								"tags.tidbcloud.com/kubernetes": kubeClusterName,
							},
						},
						Spec: cloudv1.NetworkSpec{Provider: types.ProviderTypeAWSSharedTier},
					},
				),
			},
		},
	}

	objs := []client.Object{
		&corev1.Namespace{
			ObjectMeta: metav1.ObjectMeta{
				Name: "db-bbb",
			},
		},
		&v1alpha1.TidbCluster{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "bbb",
				Namespace: "db-bbb",
			},
		},
	}
	infraObjs := []client.Object{}

	fakeAwsClient := awsfake.NewMockClient(t)
	mockAWSClient(fakeAwsClient)

	for _, c := range cases {
		infraObjsWithNetwork := infraObjs
		infraObjsWithNetwork = append(infraObjsWithNetwork, c.net)
		fakeClient := fake.NewClientBuilder().
			WithScheme(scheme.Scheme).
			WithObjects(objs...).
			Build()
		fakeInfraClient := fake.NewClientBuilder().
			WithScheme(scheme.Scheme).
			WithObjects(infraObjsWithNetwork...).
			Build()

		r := fakeReconciler(fakeClient, fakeInfraClient)
		r.AwsClient = fakeAwsClient

		net := c.net.DeepCopy()

		rescheduled, err := r.Reschedule(context.TODO(), net)
		assert.NoError(t, err, c.desc)
		assert.Equal(t, c.expectedRescheduled, rescheduled, c.desc)
		assert.Equal(t, c.expectedNetwork, net, c.desc)
		assert.Equal(t, c.expectedActions, fakeClient.Actions(), c.desc)
		assert.Equal(t, c.expectedInfraActions, fakeInfraClient.Actions(), c.desc)
	}
}
