package common_test

import (
	"context"
	"testing"
	"time"

	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/require"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpcpeering/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpcpeering/task/common"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
)

func TestGuardianDeploy(t *testing.T) {
	s := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(s))
	defaultVPCPeering := new(v1alpha1.VPCPeering)
	defaultVPC := new(v1alpha1.VPC)
	defaultInternet := new(v1alpha1.Internet)
	{
		bs, err := testdata.LoadDefaultVPCPeeringYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPCPeering))
	}
	{
		bs, err := testdata.LoadDefaultVPCYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPC))
	}
	{
		bs, err := testdata.LoadEKSInternetYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultInternet))
	}
	{
		// normal case should not return any error.
		vpcpeering := defaultVPCPeering.DeepCopy()
		delete(vpcpeering.Annotations, v1alpha1.AnnotationKeySourceVPCID)
		delete(vpcpeering.Annotations, v1alpha1.AnnotationKeySourceVPCCIDR)
		vpc := defaultVPC.DeepCopy()
		internet := defaultInternet.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpcpeering, vpc, internet).Build()
		c, err := (&task.Context{
			Local:        kubeClient,
			ProviderType: "aws",
			VPCPeering:   vpcpeering,
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
		results := d.Deploy(context.TODO())
		isAllDone := func(results []deploy.Result) bool {
			for _, r := range results {
				if !r.IsDoneState() {
					return false
				}
			}
			return true
		}
		require.Contains(t, task.VPCPeering.GetFinalizers(), v1alpha1.FinalizerVPCPeering)
		require.True(t, isAllDone(results))
		require.NoError(t, results[0].Error())
		require.Equal(t, "vpc-foo", c.VPCPeering.Annotations[v1alpha1.AnnotationKeySourceVPCID])
		require.Equal(t, "10.10.0.0/16", c.VPCPeering.Annotations[v1alpha1.AnnotationKeySourceVPCCIDR])
	}
	{
		// internet not found, should return error with requeue.
		vpcpeering := defaultVPCPeering.DeepCopy()
		delete(vpcpeering.Annotations, v1alpha1.AnnotationKeySourceVPCID)
		delete(vpcpeering.Annotations, v1alpha1.AnnotationKeySourceVPCCIDR)
		vpc := defaultVPC.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpcpeering, vpc).Build()
		c, err := (&task.Context{
			Local:        kubeClient,
			ProviderType: "aws",
			VPCPeering:   vpcpeering,
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
		results := d.Deploy(context.TODO())
		isAllDone := func(results []deploy.Result) bool {
			for _, r := range results {
				if !r.IsDoneState() {
					return false
				}
			}
			return true
		}
		require.Contains(t, task.VPCPeering.GetFinalizers(), v1alpha1.FinalizerVPCPeering)
		require.False(t, isAllDone(results))
		require.Error(t, results[0].Error())
		res, _, _ := deploy.MinRequeue(results)
		require.Greater(t, res.RequeueAfter, time.Duration(0))
	}
	{
		// source vpc not existed, should return error with requeue.
		vpcpeering := defaultVPCPeering.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpcpeering).Build()
		c, err := (&task.Context{
			Local:        kubeClient,
			ProviderType: "aws",
			VPCPeering:   vpcpeering,
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
		results := d.Deploy(context.TODO())
		isAllDone := func(results []deploy.Result) bool {
			for _, r := range results {
				if !r.IsDoneState() {
					return false
				}
			}
			return true
		}
		require.Contains(t, task.VPCPeering.GetFinalizers(), v1alpha1.FinalizerVPCPeering)
		require.False(t, isAllDone(results))
		require.Error(t, results[0].Error())
		res, _, _ := deploy.MinRequeue(results)
		require.Greater(t, res.RequeueAfter, time.Duration(0))
	}
	{
		// source vpc status is deleting, should return error without requeue.
		vpcpeering := defaultVPCPeering.DeepCopy()
		vpc := defaultVPC.DeepCopy()
		vpc.Status.Phase = v1alpha1.VPCPhaseDeleting
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpcpeering, vpc).Build()
		c, err := (&task.Context{
			Local:        kubeClient,
			ProviderType: "aws",
			VPCPeering:   vpcpeering,
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
		results := d.Deploy(context.TODO())
		isAllDone := func(results []deploy.Result) bool {
			for _, r := range results {
				if !r.IsDoneState() {
					return false
				}
			}
			return true
		}
		require.False(t, isAllDone(results))
		require.Error(t, results[0].Error())
		res, _, _ := deploy.MinRequeue(results)
		require.False(t, res.Requeue)
	}
	{
		// source vpc status is pending, should return error with requeue.
		vpcpeering := defaultVPCPeering.DeepCopy()
		vpc := defaultVPC.DeepCopy()
		vpc.Status.Phase = v1alpha1.VPCPhasePending
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpcpeering, vpc).Build()
		c, err := (&task.Context{
			Local:        kubeClient,
			ProviderType: "aws",
			VPCPeering:   vpcpeering,
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
		results := d.Deploy(context.TODO())
		isAllDone := func(results []deploy.Result) bool {
			for _, r := range results {
				if !r.IsDoneState() {
					return false
				}
			}
			return true
		}
		require.False(t, isAllDone(results))
		require.Error(t, results[0].Error())
		res, _, _ := deploy.MinRequeue(results)
		require.Greater(t, res.RequeueAfter, time.Duration(0))
	}
	{
		// vpcpeering has not finalizer, should return error with requeue at once.
		vpcpeering := defaultVPCPeering.DeepCopy()
		vpcpeering.Finalizers = []string{}
		vpc := defaultVPC.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpcpeering, vpc).Build()
		c, err := (&task.Context{
			Local:        kubeClient,
			ProviderType: "aws",
			VPCPeering:   vpcpeering,
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
		results := d.Deploy(context.TODO())
		isAllDone := func(results []deploy.Result) bool {
			for _, r := range results {
				if !r.IsDoneState() {
					return false
				}
			}
			return true
		}
		require.Contains(t, task.VPCPeering.GetFinalizers(), v1alpha1.FinalizerVPCPeering)
		require.False(t, isAllDone(results))
		require.Error(t, results[0].Error())
		res, _, _ := deploy.MinRequeue(results)
		require.Equal(t, res.RequeueAfter, time.Duration(0))
	}
}
