package fixtures

import (
	"context"
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/pingcap/errors"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	ec2v1beta1 "github.com/crossplane/provider-aws/apis/ec2/v1beta1"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/test/e2ev2/framework"
)

var _ = Describe("network", func() {
	var (
		f   = framework.DefaultFramework
		p   = f.Provider
		ctx = context.Background()
		c   = f.InfraCli
		t   = f.TestContext
		ns  *corev1.Namespace
		vpc *ec2v1beta1.VPC
	)

	BeforeEach(func() {
		ns = &corev1.Namespace{
			ObjectMeta: metav1.ObjectMeta{
				GenerateName: fmt.Sprintf("e2e-%s-", p.Name()),
			},
		}
		f.ExpectNoError(c.Create(ctx, ns), "create namespace")
		f.Log("namespace: %s", ns.Name)
		// If provider is supported, create a vpc for testing
		if p.SupportNetworkPeering() {
			vpc = p.NewPeerVPC(ctx)
			f.ExpectNoError(c.Create(ctx, vpc), "create VPC")
			f.Log("VPC: %s", vpc.Name)
		}
	})

	AfterEach(func() {
		f.ExpectNoError(c.Delete(ctx, ns), "delete namespace")
		// Because VPC is cluster-scoped resources, so we should delete it by CrossPlane API
		// AfterEach will already called even if the test failed.
		if p.SupportNetworkPeering() {
			f.ExpectNoError(c.Delete(ctx, vpc), fmt.Sprintf("delete VPC: %s", vpc.Name))
		}
	})

	It("network capability", func() {
		network := p.NewNetwork(ctx)
		network.Namespace = ns.Name
		f.ExpectNoError(c.Create(ctx, network), "create network")
		f.Log("network: %s", network.Name)

		Eventually(func() error {
			err := c.Get(ctx, types.NamespacedName{Namespace: network.Namespace, Name: network.Name}, network)
			if err != nil {
				return err
			}

			if len(network.Finalizers) == 0 {
				return errors.Errorf("network %q finalizers is empty", network.Name)
			}

			if network.Status.Phase != cloudv1.NetworkPhaseNormal {
				return errors.New("network status is not normal")
			}

			return nil

		}, t.NetworkCreateTimeout, t.Interval).Should(Succeed(), fmt.Sprintf("network status to normal within %v", t.NetworkCreateTimeout))

		if p.SupportNetworkPeering() {
			f.ExpectNoError(c.Get(ctx, types.NamespacedName{Namespace: vpc.Namespace, Name: vpc.Name}, vpc))
			condition := vpc.Status.ConditionedStatus.GetCondition(xpv1.TypeReady)
			Expect(condition.Status).Should(Equal(corev1.ConditionTrue))

			np := &cloudv1.NetworkPeer{
				ObjectMeta: metav1.ObjectMeta{
					GenerateName: fmt.Sprintf("e2e-%s-", p.Name()),
					Namespace:    ns.Name,
					// inherit label from network
					Labels: network.Labels,
				},
				Spec: cloudv1.NetworkPeerSpec{
					Source: network.Name,
					Target: cloudv1.Target{
						AWS: &cloudv1.AWS{
							AccountID:  vpc.Status.AtProvider.OwnerID,
							VpcID:      vpc.Annotations["crossplane.io/external-name"],
							PeerCIDR:   vpc.Spec.ForProvider.CIDRBlock,
							PeerRegion: *vpc.Spec.ForProvider.Region,
						},
					},
				},
			}
			f.ExpectNoError(c.Create(ctx, np), "create network peer")
			f.Log("network peer: %s", np.Name)

			Eventually(func() error {
				err := c.Get(ctx, types.NamespacedName{Namespace: np.Namespace, Name: np.Name}, np)
				if err != nil {
					return err
				}

				if np.Status.Phase != cloudv1.NetworkPeerPhaseActive {
					return errors.New("networkpeering status is not active")
				}

				return nil

			}, t.NetworkPeeringActiveTimeout, t.Interval).Should(Succeed(), fmt.Sprintf("networkpeering status to active within %v", t.NetworkPeeringActiveTimeout))

			f.ExpectNoError(c.Delete(ctx, np), "delete network peer")
		}

		tenantCli, err := p.ConnectNetwork(ctx, c, network)
		f.ExpectNoError(err, "connect to network")

		f.ExpectNoError(p.AfterNetworkCreated(ctx, c, tenantCli, network), "after network created")

		f.ExpectNoError(c.Delete(ctx, network), "delete network")

		Eventually(func() error {
			err := c.Get(ctx, types.NamespacedName{Namespace: network.Namespace, Name: network.Name}, network)
			if err != nil && !apierrors.IsNotFound(err) {
				return err
			}
			if apierrors.IsNotFound(err) {
				return nil
			}
			return errors.New("network is not deleted")
		}, t.NetworkDeleteTimeout, t.Interval).Should(Succeed(), fmt.Sprintf("network is deleted within %v", t.NetworkDeleteTimeout))

		f.ExpectNoError(p.AfterNetworkDeleted(ctx, c, tenantCli, network), "after network deleted")
	})
})
