package internet

import (
	"context"
	"testing"

	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	"github.com/upbound/provider-aws/apis/ec2/v1beta1"
	"go.uber.org/mock/gomock"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/record"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/aliyun"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/internet/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
)

func getFakeInternet(t *testing.T) *v1alpha1.Internet {
	fakeInternet := &v1alpha1.Internet{}
	bs, err := testdata.LoadEKSInternetYAML()
	assert.Nil(t, err)
	assert.Nil(t, yaml.Unmarshal(bs, fakeInternet))
	return fakeInternet
}

func fakeCli(t *testing.T) client.Client {
	s := runtime.NewScheme()
	v1alpha1.AddToScheme(s)
	v1beta1.SchemeBuilder.AddToScheme(s)
	fakeInternet := getFakeInternet(t)
	cli := fake.NewClientBuilder().WithScheme(s).WithObjects(fakeInternet).WithStatusSubresource(fakeInternet).Build()
	return cli
}

func Test_updateInternet(t *testing.T) {
	fakeInternet := getFakeInternet(t)
	cli := fakeCli(t)
	internetrw := repository.NewInternetReaderWriter(cli)
	fakeVPCID := "vpc-123456"
	assert.Nil(t, internetrw.Get(context.Background(), types.NamespacedName{
		Namespace: fakeInternet.Namespace,
		Name:      fakeInternet.Name,
	}, fakeInternet))
	fakeInternet.Status.VPCID = fakeVPCID
	assert.Nil(t, updateInternet(context.Background(), internetrw, fakeInternet))
	assert.Nil(t, internetrw.Get(context.Background(), types.NamespacedName{
		Namespace: fakeInternet.Namespace,
		Name:      fakeInternet.Name,
	}, fakeInternet))
	assert.Equal(t, fakeVPCID, fakeInternet.GetLabels()[v1alpha1.VPCIDKey])
}

func TestInternetReconciler_updateCreate(t *testing.T) {
	fakeConfig := config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			Provider: consts.ProviderTypeEKS,
		},
	}
	cli := fakeCli(t)
	ctl := gomock.NewController(t)
	aliyunCli := aliyun.NewMockClient(ctl)
	recorder := record.NewFakeRecorder(10)
	reconciler := NewInternetReconciler(cli, logr.Discard(), cli.Scheme(), recorder, fakeConfig, aliyunCli)
	fakeInternet := getFakeInternet(t)
	t.Run("updateCreate success", func(t *testing.T) {
		getInternet := &v1alpha1.Internet{}
		assert.Nil(t, cli.Get(context.Background(), client.ObjectKey{Name: fakeInternet.Name, Namespace: fakeInternet.Namespace}, getInternet))
		getInternet.Status.Phase = v1alpha1.InternetPhaseCreating
		result, err := reconciler.updateCreate(context.Background(), logr.Discard(), &task.Context{
			Internet:   getInternet,
			InternetRW: repository.NewInternetReaderWriter(cli),
		}, []deploy.Result{})
		assert.Nil(t, err)
		assert.Equal(t, ctrl.Result{}, result)
		assert.Equal(t, v1alpha1.InternetPhaseActive, getInternet.Status.Phase)
		assert.Equal(t, "", getInternet.Status.Reason)
	})
	t.Run("updateCreate failed", func(t *testing.T) {
		getInternet := &v1alpha1.Internet{}
		assert.Nil(t, cli.Get(context.Background(), client.ObjectKey{Name: fakeInternet.Name, Namespace: fakeInternet.Namespace}, getInternet))
		getInternet.Status.Phase = v1alpha1.InternetPhaseCreating
		result, err := reconciler.updateCreate(context.Background(), logr.Discard(), &task.Context{
			Internet:   getInternet,
			InternetRW: repository.NewInternetReaderWriter(cli),
		}, []deploy.Result{deploy.Panic(assert.AnError)})
		assert.ErrorIs(t, err, assert.AnError)
		assert.Equal(t, ctrl.Result{}, result)
		assert.Equal(t, v1alpha1.InternetPhaseCreating, getInternet.Status.Phase)
		assert.Contains(t, getInternet.Status.Reason, "assert.AnError")
	})
}

func TestInternetReconciler_updateDelete(t *testing.T) {
	fakeConfig := config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			Provider: consts.ProviderTypeEKS,
		},
	}
	cli := fakeCli(t)
	ctl := gomock.NewController(t)
	aliyunCli := aliyun.NewMockClient(ctl)
	recorder := record.NewFakeRecorder(10)
	reconciler := NewInternetReconciler(cli, logr.Discard(), cli.Scheme(), recorder, fakeConfig, aliyunCli)
	fakeInternet := getFakeInternet(t)
	t.Run("updateDelete success", func(t *testing.T) {
		getInternet := &v1alpha1.Internet{}
		getInternet.Status.Phase = v1alpha1.InternetPhaseDeleting
		getInternet.Finalizers = []string{v1alpha1.Finalizer}
		err := cli.Get(context.Background(), client.ObjectKey{Name: fakeInternet.Name, Namespace: fakeInternet.Namespace}, getInternet)
		result, err := reconciler.updateDelete(context.Background(), logr.Discard(), &task.Context{
			Internet:   getInternet,
			InternetRW: repository.NewInternetReaderWriter(cli),
		}, []deploy.Result{})
		assert.Nil(t, err)
		assert.Equal(t, ctrl.Result{}, result)
		assert.Nil(t, getInternet.GetFinalizers())
	})
	t.Run("updateDelete failed", func(t *testing.T) {
		getInternet := &v1alpha1.Internet{}
		getInternet.Status.Phase = v1alpha1.InternetPhaseDeleting
		err := cli.Get(context.Background(), client.ObjectKey{Name: fakeInternet.Name, Namespace: fakeInternet.Namespace}, getInternet)
		result, err := reconciler.updateDelete(context.Background(), logr.Discard(), &task.Context{
			Internet:   getInternet,
			InternetRW: repository.NewInternetReaderWriter(cli),
		}, []deploy.Result{deploy.Panic(assert.AnError)})
		assert.ErrorIs(t, err, assert.AnError)
		assert.Equal(t, ctrl.Result{}, result)
	})
}

func Test_isNotReadyInternet(t *testing.T) {
	cases := []struct {
		name string
		vpc  *v1alpha1.Internet
		want bool
	}{
		{
			name: "internet phase is empty",
			vpc: &v1alpha1.Internet{
				Status: v1alpha1.InternetStatus{
					Phase: v1alpha1.InternetPhasePending,
				},
			},
			want: true,
		},
		{
			name: "internet phase is active",
			vpc: &v1alpha1.Internet{
				Status: v1alpha1.InternetStatus{
					Phase: v1alpha1.InternetPhaseActive,
				},
			},
			want: false,
		},
		{
			name: "internet phase is creating",
			vpc: &v1alpha1.Internet{
				Status: v1alpha1.InternetStatus{
					Phase: v1alpha1.InternetPhaseCreating,
				},
			},
			want: true,
		},
		{
			name: "internet phase is failed",
			vpc: &v1alpha1.Internet{
				Status: v1alpha1.InternetStatus{
					Phase: v1alpha1.InternetPhaseFailed,
				},
			},
			want: true,
		},
		{
			name: "internet phase is deleting",
			vpc: &v1alpha1.Internet{
				Status: v1alpha1.InternetStatus{
					Phase: v1alpha1.InternetPhaseDeleting,
				},
			},
			want: false,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			assert.Equal(t, tc.want, isNotReadyInternet(tc.vpc))
		})
	}
}
