package eks

import (
	"context"
	"testing"
	"time"

	ec2v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1alpha1"
	"github.com/crossplane-contrib/provider-aws/apis/ec2/v1beta1"
	ec2v1beta1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpc/task"
	resvpc "github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/vpc"
	"github.com/tidbcloud/infra-provider/regional-server/test/kubemock"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
	"go.uber.org/mock/gomock"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"
)

func fakeTaskContext(t *testing.T) *task.Context {
	s := runtime.NewScheme()
	v1alpha1.AddToScheme(s)
	ec2v1beta1.SchemeBuilder.AddToScheme(s)
	ec2v1alpha1.SchemeBuilder.AddToScheme(s)
	fakeVPC := &v1alpha1.VPC{}
	bs, err := testdata.LoadDefaultVPCYAML()
	assert.Nil(t, err)
	assert.Nil(t, yaml.Unmarshal(bs, fakeVPC))
	cli := fake.NewClientBuilder().WithScheme(s).WithObjects(fakeVPC).WithStatusSubresource(fakeVPC, &ec2v1beta1.VPC{}, &v1beta1.SecurityGroup{}).Build()
	c := &task.Context{
		Local:  k8s.NewMutClientWithOwner(cli, fakeVPC.DeepCopy(), namer.NewNamer()).Client(),
		Logger: logr.Discard(),
		VPC:    fakeVPC.DeepCopy(),
		VPCRW:  resvpc.NewVPCReaderWriter(cli),
		Config: config.RootConfig{},
	}
	c, err = c.Default()
	assert.Nil(t, err)
	return c
}

func fakeVPCTask(t *testing.T) *VPC {
	return NewVPC(fakeTaskContext(t))
}

func TestVPC_Create(t *testing.T) {
	vpcTask := fakeVPCTask(t)
	t.Run("Create aws vpc success", func(t *testing.T) {
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		awsVPC := &ec2v1beta1.VPC{}
		name := namer.UniqueIdentifier(vpcTask.VPC.ObjectMeta)
		err := vpcTask.Local.Get(context.Background(), types.NamespacedName{Name: name}, awsVPC)
		assert.Nil(t, err)
		assert.Equal(t, v1beta1.VPCSpec{
			ForProvider: v1beta1.VPCParameters{
				Region:             pointer.String(vpcTask.VPC.Spec.Region),
				CIDRBlock:          vpcTask.VPC.Spec.CIDR,
				EnableDNSSupport:   pointer.Bool(true),
				EnableDNSHostNames: pointer.Bool(true),
				Tags:               util.GetEC2V1BetaTags(util.AddResourceName(cloudv1.TagsFromLabels(vpcTask.VPC.Labels), name)),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: vpcTask.VPC.Spec.AccountRef.ProviderConfigName},
			},
		}, awsVPC.Spec)
	})
	t.Run("Create aws vpc failed", func(t *testing.T) {
		ctl := gomock.NewController(t)
		mockcli := kubemock.NewMockClient(ctl)
		vpcTask.Local = mockcli
		mockcli.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "not found")).Times(1)
		mockcli.EXPECT().Create(gomock.Any(), gomock.Any()).Return(assert.AnError).Times(1)
		result := vpcTask.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.NotNil(t, retriableErr)
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})
}

func TestVPC_setVPCStatus(t *testing.T) {
	vpcTask := fakeVPCTask(t)
	vpcID := "vpc-123456"
	vpcName := "test"
	vpcRes := &v1beta1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Name: vpcName,
			Annotations: map[string]string{
				meta.AnnotationKeyExternalName: vpcID,
			},
		},
	}
	t.Run("Set vpc info to vpc status", func(t *testing.T) {
		err := vpcTask.setVPCStatus(context.Background(), vpcRes)
		assert.Nil(t, err)
		assert.Equal(t, v1alpha1.VPCRes{VPCID: vpcID, Name: vpcName}, vpcTask.VPC.Status.VPC)
	})
}

func TestVPC_eksClusterNameTagKey(t *testing.T) {
	assert.Equal(t, "kubernetes.io/cluster/test", eksClusterNameTagKey("test"))
}

func TestVPC_PostCreate(t *testing.T) {
	t.Run("aws vpc is not found", func(t *testing.T) {
		vpcTask := fakeVPCTask(t)
		vpcTask.VPC = &v1alpha1.VPC{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "test-not-found",
				Namespace: "default",
			},
		}
		result := vpcTask.PostCreate(context.Background())
		res, _, _ := deploy.MinRequeue([]deploy.Result{result})
		assert.Equal(t, res.RequeueAfter, time.Second*30)
	})
	t.Run("aws vpc is not available", func(t *testing.T) {
		vpcTask := fakeVPCTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = vpcTask.PostCreate(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.NotNil(t, retriableErr)
		assert.Equal(t, ": vpc status not available: ", retriableErr.Error())
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})

	t.Run("aws vpc is available", func(t *testing.T) {
		vpcTask := fakeVPCTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		awsVPC := &ec2v1beta1.VPC{}
		name := namer.UniqueIdentifier(vpcTask.VPC.ObjectMeta)
		err := vpcTask.Local.Get(context.Background(), types.NamespacedName{Name: name}, awsVPC)
		assert.Nil(t, err)
		awsVPC.Status.AtProvider.VPCState = "available"
		err = vpcTask.Local.Status().Update(context.Background(), awsVPC)
		assert.Nil(t, err)
		result = vpcTask.PostCreate(context.Background())
		assert.True(t, result.IsDoneState())
	})
}

func TestVPC_Delete(t *testing.T) {
	vpcTask := fakeVPCTask(t)
	t.Run("Delete aws vpc success", func(t *testing.T) {
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = vpcTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
	})
	t.Run("aws vpc not found", func(t *testing.T) {
		ctl := gomock.NewController(t)
		mockcli := kubemock.NewMockClient(ctl)
		vpcTask.Local = mockcli
		mockcli.EXPECT().Delete(gomock.Any(), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "not found")).Times(1)
		result := vpcTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
	})
}

func TestVPC_PostDelete(t *testing.T) {
	t.Run("aws vpc is not found", func(t *testing.T) {
		vpcTask := fakeVPCTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = vpcTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
		result = vpcTask.PostDelete(context.Background())
		assert.True(t, result.IsDoneState())
	})
	t.Run("aws vpc is not deleted", func(t *testing.T) {
		vpcTask := fakeVPCTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		result = vpcTask.PostDelete(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.NotNil(t, err)
		assert.Nil(t, retriableErr)
		assert.False(t, res.Requeue)
	})
}
