package cluster

import (
	"context"
	"time"

	"github.com/aws/aws-sdk-go-v2/service/ec2"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/crossplane/provider-aws/apis/ec2/v1alpha1"
	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	"k8s.io/client-go/util/retry"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/shared-provider/tests/e2e/common/data"
	"github.com/tidbcloud/shared-provider/tests/e2e/common/helper"
	"github.com/tidbcloud/shared-provider/tests/e2e/common/labels"
	"github.com/tidbcloud/shared-provider/tests/e2e/common/wait"
	"github.com/tidbcloud/shared-provider/tests/framework"
)

const (
	arbitraryDNSResolveTime = 2 * time.Minute
)

var _ = ginkgo.Describe("Cluster", func() {
	f := framework.New()

	framework.It(labels.With(
		labels.TCluster,
		labels.FCore,
		labels.Feature("Core"),
	), "create cluster", func() {
		ctx := context.Background()
		_, cluster, _ := helper.Create(
			ctx, f, helper.TypeCluster, true, "",
		)

		err := wait.WaitForCondition(ctx, f.InfraClient, cluster, func() bool {
			return cluster.Status.Phase == cloudv1.ClusterPhaseRunning
		}, helper.LongTaskTimeout)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())
	})

	framework.It(labels.With(
		labels.TCluster,
		labels.FCore,
	), "private link", func() {
		ctx := context.Background()
		net, cluster, _ := helper.Create(
			ctx, f, helper.TypeCluster, true, "",
			data.ClusterWithPrivateLink(f.AWSCallerIdentity, ""),
		)

		err := wait.WaitForCondition(ctx, f.InfraClient, cluster, func() bool {
			_, ok := cluster.Annotations["alpha.privatelink.tidbcloud.com/status"]
			return ok
		}, helper.LongTaskTimeout)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())

		ginkgo.By("Create vpc endpoint")
		ep := helper.CreateAWSVPCEndpointForTiDB(ctx, f, net, cluster)

		ginkgo.By("Add vpc endpoint into privateLink whitelist")
		p := data.ClusterWithPrivateLink(f.AWSCallerIdentity, meta.GetExternalName(ep.VPCEndpoint))
		err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
			if err := f.InfraClient.Get(ctx, client.ObjectKeyFromObject(cluster), cluster); err != nil {
				return err
			}
			return f.InfraClient.Update(ctx, p(cluster))
		})
		gomega.Expect(err).NotTo(gomega.HaveOccurred())

		ginkgo.By("Wait for vpc endpoint available")
		err = wait.WaitForCondition(ctx, f.Client, ep.VPCEndpoint, func() bool {
			if ep.VPCEndpoint.Status.AtProvider.VPCEndpoint.State == nil {
				return false
			}
			if *ep.VPCEndpoint.Status.AtProvider.VPCEndpoint.State == "available" {
				return true
			}
			return false

		}, helper.LongTaskTimeout)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())

		ginkgo.By("Enable private dns name")
		// FIXME(liubo02): wait for https://github.com/crossplane-contrib/provider-aws/issues/1363 fixed
		ac := ec2.NewFromConfig(f.AWSConfig)
		vpceID := meta.GetExternalName(ep.VPCEndpoint)
		_, err = ac.ModifyVpcEndpoint(ctx, &ec2.ModifyVpcEndpointInput{
			VpcEndpointId:     &vpceID,
			PrivateDnsEnabled: pointer.Bool(true),
		})
		gomega.Expect(err).NotTo(gomega.HaveOccurred())

		err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
			// FIXME(liubo02): wait for https://github.com/crossplane-contrib/provider-aws/issues/1363 fixed
			ep.VPCEndpoint.Status = v1alpha1.VPCEndpointStatus{}

			ep.VPCEndpoint.Spec.ForProvider.PrivateDNSEnabled = pointer.Bool(true)
			if err := f.Client.Get(ctx, client.ObjectKeyFromObject(ep.VPCEndpoint), ep.VPCEndpoint); err != nil {
				return err
			}
			return f.Client.Update(ctx, ep.VPCEndpoint)
		})
		gomega.Expect(err).NotTo(gomega.HaveOccurred())

		err = wait.WaitForCondition(ctx, f.Client, ep.VPCEndpoint, func() bool {
			if ep.VPCEndpoint.Status.AtProvider.VPCEndpoint.State == nil {
				return false
			}
			if *ep.VPCEndpoint.Status.AtProvider.VPCEndpoint.State == "available" &&
				// the third entry is private dns
				len(ep.VPCEndpoint.Status.AtProvider.VPCEndpoint.DNSEntries) == 3 {
				return true
			}
			return false

		}, helper.LongTaskTimeout)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())

		ginkgo.By("Create lambada")
		fn := helper.CreateLambda(ctx, f, ep)

		// NOTE(liubo02): wait for DNS resolved
		// TODO(liubo02): change to retry invoke many times
		time.Sleep(arbitraryDNSResolveTime)
		fn.Invoke(ctx)

		err = f.InfraClient.Delete(ctx, cluster)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())

		err = wait.WaitForObjectDeleted(ctx, f.InfraClient, cluster, helper.LongTaskTimeout)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())

		helper.DeleteLambda(ctx, f, fn)
		helper.DeleteAWSVPCEndpointForTiDB(ctx, f, ep)
	})

})
