package aws_test

import (
	"context"
	"testing"

	"github.com/aws/aws-sdk-go-v2/service/ec2"
	ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
	xpec2v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1alpha1"
	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/require"
	"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"
	"go.uber.org/mock/gomock"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/utils/pointer"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"
)

func TestVPCPeeringAccept(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 = []ec2types.RouteTable{
				{
					RouteTableId: ptr.To("rtb-foobar"),
				},
			}
			return out, nil
		}).Times(1)
	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
		}).Times(1)
	ec2client.EXPECT().ModifyVpcPeeringConnectionOptions(
		gomock.Any(),
		gomock.Any(),
		gomock.Any()).
		DoAndReturn(func(ctx context.Context, params *ec2.ModifyVpcPeeringConnectionOptionsInput, optFns ...func(*ec2.Options)) (*ec2.ModifyVpcPeeringConnectionOptionsOutput, error) {
			out := new(ec2.ModifyVpcPeeringConnectionOptionsOutput)
			return out, nil
		}).Times(1)
	ec2client.EXPECT().AcceptVpcPeeringConnection(
		gomock.Any(),
		gomock.Any(),
		gomock.Any()).
		DoAndReturn(func(ctx context.Context, params *ec2.AcceptVpcPeeringConnectionInput, optFns ...func(*ec2.Options)) (*ec2.AcceptVpcPeeringConnectionOutput, error) {
			out := new(ec2.AcceptVpcPeeringConnectionOutput)
			return out, nil
		}).Times(1)
	require.NoError(t, v1alpha1.SchemeBuilder.AddToScheme(s))
	require.NoError(t, xpec2v1alpha1.SchemeBuilder.AddToScheme(s))
	defaultVPCPeering := &v1alpha1.VPCPeering{
		Spec: v1alpha1.VPCPeeringSpec{
			Provider: "aws",
			Region:   "eastus",
			SourceVPCRef: &v1alpha1.Reference{
				Name:      "foo",
				Namespace: "foo",
			},
			VPCPeeringConnectionID: "pcx-foobar",
			PeeringRole:            v1alpha1.PeeringRoleAccepter,
			Target: v1alpha1.Target{
				AWS: &v1alpha1.AWS{
					PeerCIDR: "10.3.4.5/24",
				},
			},
		},
	}
	defaultVPC := new(v1alpha1.VPC)
	{
		bs, err := testdata.LoadDefaultVPCYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPC))
	}
	defaultInternet := new(v1alpha1.Internet)
	{
		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()
		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.NewVPCPeeringAccept(c)
		{
			d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
			results := d.Deploy(context.TODO())
			require.NotNil(t, results[0].Error())
			require.Equal(t, "true", c.VPCPeering.Annotations[v1alpha1.AnnotationKeyVPCPeeringApproved])

			d2 := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
			results2 := d2.Deploy(context.TODO())
			require.True(t, isAllDone(results2), "deploy results: %v", results2)
			require.Equal(t, "true", c.VPCPeering.Annotations[v1alpha1.AnnotationKeyVPCPeeringApproved])
			require.Equal(t, "true", c.VPCPeering.Annotations[v1alpha1.AnnotationKeyVPCPeeringAllowRemoteDNS])
			require.Equal(t, "true", c.Internet.Annotations[v1alpha1.AnnotationKeyRouteTableEnsuredAcceptorPeeringPrefix+c.VPCPeering.Name])
			require.Equal(t, v1alpha1.VPCPeeringPhaseActive, c.VPCPeering.Status.Phase)
			// ↑ update crossplane cr
		}
		{
			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 = []ec2types.RouteTable{
						{
							RouteTableId: ptr.To("rtb-foobar"),
							Routes: []ec2types.Route{
								{
									VpcPeeringConnectionId: ptr.To("pcx-foobar"),
								},
							},
						},
					}
					return out, nil
				}).Times(1)
			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
				}).Times(1)
			ec2client.EXPECT().DeleteVpcPeeringConnection(
				gomock.Any(),
				gomock.Any(),
				gomock.Any()).
				DoAndReturn(func(ctx context.Context, params *ec2.DeleteVpcPeeringConnectionInput, optFns ...func(*ec2.Options)) (*ec2.DeleteVpcPeeringConnectionOutput, error) {
					out := new(ec2.DeleteVpcPeeringConnectionOutput)
					return out, nil
				}).Times(1)
			// delete under noraml case should delete route
			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)
		}

	}
}
