package aws_test

import (
	"context"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go-v2/service/ec2"
	ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"
	_ "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/utils/pointer"
	_ "sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	xpec2v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"

	"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/aws"
	"github.com/tidbcloud/infra-provider/regional-server/test/service/mockec2client"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
)

func TestVPCPeeringConnection(t *testing.T) {
	s := runtime.NewScheme()
	ctl := gomock.NewController(t)
	// just mock the api to make test case work
	// not really check the calling logic correct.
	ec2client := mockec2client.NewMockClient(ctl)
	ec2client.EXPECT().DescribeRouteTables(
		gomock.Any(),
		gomock.Any(),
		gomock.Any()).
		DoAndReturn(func(_ context.Context, _ *ec2.DescribeRouteTablesInput, _ ...func(*ec2.Options)) (*ec2.DescribeRouteTablesOutput, error) {
			out := new(ec2.DescribeRouteTablesOutput)
			out.RouteTables = make([]ec2types.RouteTable, 0)
			return out, nil
		}).AnyTimes()
	ec2client.EXPECT().CreateRoute(
		gomock.Any(),
		gomock.Any(),
		gomock.Any()).
		DoAndReturn(func(ctx context.Context, params *ec2.CreateRouteInput, optFns ...func(*ec2.Options)) (*ec2.CreateRouteOutput, error) {
			out := new(ec2.CreateRouteOutput)
			out.Return = pointer.Bool(true)
			return out, nil
		}).AnyTimes()
	ec2client.EXPECT().DeleteRoute(
		gomock.Any(),
		gomock.Any(),
		gomock.Any()).
		DoAndReturn(func(ctx context.Context, params *ec2.DeleteRouteInput, optFns ...func(*ec2.Options)) (*ec2.DeleteRouteOutput, error) {
			out := new(ec2.DeleteRouteOutput)
			return out, nil
		}).AnyTimes()
	require.NoError(t, v1alpha1.SchemeBuilder.AddToScheme(s))
	require.NoError(t, xpec2v1alpha1.SchemeBuilder.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))
	}
	{
		// deploy under normal case should return without error.
		vpcpeering := defaultVPCPeering.DeepCopy()
		vpcpeering.Status.Phase = v1alpha1.VPCPeeringPhaseCreating
		vpcpeering.Status.ExternalName = ""
		vpcpeering.Status.CrossplaneResourceName = ""
		vpcpeering.Spec.Provider = "aws"
		delete(vpcpeering.Annotations, v1alpha1.LabelKeyExternalName)
		vpc := defaultVPC.DeepCopy()
		internet := defaultInternet.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpcpeering, vpc, internet).WithStatusSubresource(vpcpeering, vpc, &xpec2v1alpha1.VPCPeeringConnection{}).Build()
		c, err := (&task.Context{
			Local:        kubeClient,
			EC2Client:    ec2client,
			ProviderType: "aws",
			VPCPeering:   vpcpeering,
			VPC:          vpc,
			Internet:     internet,
		}).Default()
		require.NoError(t, err)
		task := aws.NewVCPPeeringConnection(c)
		{
			d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
			results := d.Deploy(context.TODO())
			require.Contains(t, task.VPCPeering.GetFinalizers(), v1alpha1.FinalizerVPCPeering)
			require.NoError(t, results[0].Error())
			require.True(t, isAllDone(results), "deploy results: %v", results)
			res, _, _ := deploy.MinRequeue(results)
			require.Greater(t, res.RequeueAfter, time.Duration(0))
			list := new(xpec2v1alpha1.VPCPeeringConnectionList)
			require.NoError(t, kubeClient.List(context.TODO(), list))
			require.Len(t, list.Items, 1)
			peeringConn := &list.Items[0]
			require.Equal(t, "foo", peeringConn.Spec.ResourceSpec.ProviderConfigReference.Name)
			require.Equal(t, "vpc-foo", *peeringConn.Spec.ForProvider.VPCID)
			require.Equal(t, "us-west-2", *&peeringConn.Spec.ForProvider.Region)
			require.Equal(t, "bar", *peeringConn.Spec.ForProvider.PeerOwnerID)
			require.Equal(t, "vpc-bar", *peeringConn.Spec.ForProvider.PeerVPCID)
			require.Contains(t, c.VPCPeering.Status.CrossplaneResourceName, peeringConn.Name)
			// the requester peering option should be set while the accepter peering option should be nil
			require.NotNil(t, peeringConn.Spec.ForProvider.RequesterPeeringOptions)
			require.True(t, *peeringConn.Spec.ForProvider.RequesterPeeringOptions.AllowDNSResolutionFromRemoteVPC)
			require.Nil(t, peeringConn.Spec.ForProvider.AccepterPeeringOptions)
			// ↓ update crossplane cr
			peeringConn.Annotations["crossplane.io/external-name"] = "pcx-foobar"
			peeringConn.Status.SetConditions(xpv1.Available(), xpv1.ReconcileSuccess())
			peeringConn.Status.AtProvider.VPCPeeringConnectionID = pointer.String("pcx-foobar")
			require.NoError(t, kubeClient.Status().Update(context.TODO(), peeringConn))
			require.NoError(t, kubeClient.Update(context.TODO(), peeringConn))
			// ↑ update crossplane cr
		}
		{
			// check status and anno on vpcpeering cr again after crossplane cr being updated
			d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
			results := d.Deploy(context.TODO())
			require.NoError(t, results[0].Error())
			require.True(t, isAllDone(results), "deploy results: %v", results)
			res, _, _ := deploy.MinRequeue(results)
			require.Greater(t, res.RequeueAfter, time.Duration(0))
			require.Equal(t, "pcx-foobar", c.VPCPeering.Labels[v1alpha1.LabelKeyExternalName])
			require.Equal(t, "pcx-foobar", c.VPCPeering.Status.ExternalName)
			require.Equal(t, v1alpha1.VPCPeeringPhaseActive, c.VPCPeering.Status.Phase)
			require.Equal(t, "true", internet.Annotations[v1alpha1.AnnotationKeyRouteTableEnsuredPeeringPrefix+vpcpeering.Name])
		}
		{
			// delete under noraml case should delete crossplane cr
			d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
			results := d.Destroy(context.TODO())
			require.NoError(t, results[0].Error())
			require.True(t, isAllDone(results), "deploy results: %v", results)
			t.Logf("%#v", results)
			res, _, _ := deploy.MinRequeue(results)
			require.Equal(t, res.RequeueAfter, time.Duration(0))
			require.False(t, res.Requeue)
			list := new(xpec2v1alpha1.VPCPeeringConnectionList)
			require.NoError(t, kubeClient.List(context.TODO(), list))
			require.Len(t, list.Items, 0)
		}

	}
}

func isAllDone(results []deploy.Result) bool {
	for _, r := range results {
		if !r.IsDoneState() {
			return false
		}
	}
	return true
}
