package integration

import (
	"context"
	"time"

	awsvpcpeeringv1alpha1 "github.com/crossplane/provider-aws/apis/vpcpeering/v1alpha1"
	gcpvpcpeeringv1beta1 "github.com/crossplane/provider-gcp/apis/vpcpeering/v1beta1"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/controllers/networkpeer"
)

var _ = Describe("Networkpeer controller", func() {

	// Define utility constants for object names and testing timeouts/durations and intervals.
	const (
		timeout  = time.Second * 120
		interval = time.Second * 1
	)

	Context("When reconcile networkpeer", func() {
		It("Should reconcile AWS networkpeer", func() {
			ctx := context.Background()
			namespace := "aws-peer-test"
			ns := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: namespace,
				},
			}
			Expect(k8sClient.Create(ctx, ns)).To(Succeed())

			nt := &cloudv1.Network{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "network-" + cloudv1.RandomString(8),
					Namespace: namespace,
				},
				Spec: cloudv1.NetworkSpec{
					Provider: "aws",
					Region:   "us-east-2",
					CIDR:     "172.10.0.0/16",
				},
			}
			Expect(k8sClient.Create(ctx, nt)).To(Succeed())
			Eventually(func() error {
				_, err := networkNormal(ctx, nt, true)
				return err
			}, timeout, interval).Should(Succeed(), "wait network ready")
			By("Create aws networkpeer resource")
			np := &cloudv1.NetworkPeer{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "networkpeer-" + cloudv1.RandomString(8),
					Namespace: namespace,
				},
				Spec: cloudv1.NetworkPeerSpec{
					Source: nt.Name,
					Target: cloudv1.Target{
						AWS: &cloudv1.AWS{
							AccountID:  "123",
							VpcID:      "vpc-" + cloudv1.RandomString(17),
							PeerCIDR:   "10.1.0.0/18",
							PeerRegion: "us-east-2",
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, np)).To(Succeed())
			Eventually(func() bool {
				if err := k8sClient.Get(ctx, types.NamespacedName{Name: np.ObjectMeta.Name, Namespace: np.ObjectMeta.Namespace}, np); err != nil {
					return false
				}
				return controllerutil.ContainsFinalizer(np, networkpeer.NetworkpeerFinalizer)
			}, timeout, interval).Should(BeTrue(), "should add finalizer to Networkpeer")

			awsnp := &awsvpcpeeringv1alpha1.VPCPeeringConnection{}
			Eventually(func() bool {
				err := k8sClient.Get(ctx, types.NamespacedName{Name: np.ClusterScopeName()}, awsnp)
				if err != nil {
					return false
				}
				readyCondition := &xpv1.Condition{
					Type:   xpv1.TypeReady,
					Status: corev1.ConditionTrue,
					LastTransitionTime: metav1.Time{
						Time: time.Now(),
					},
				}
				awsnp.Status.ResourceStatus.ConditionedStatus.Conditions = append(awsnp.Status.ResourceStatus.ConditionedStatus.Conditions, *readyCondition)

				return (k8sClient.Status().Update(ctx, awsnp) == nil)

			}, timeout, interval).Should(BeTrue(), "external obj should be ready")

			Eventually(func() bool {
				err := k8sClient.Get(ctx, types.NamespacedName{Namespace: np.ObjectMeta.Namespace, Name: np.ObjectMeta.Name}, np)
				if err != nil {
					return false
				}
				return (np.Status.Phase == cloudv1.NetworkPeerPhaseActive)
			}, timeout, interval).Should(BeTrue(), "should be Available phase")

			By("Delete aws networkpeer resource")
			Expect(k8sClient.Delete(ctx, np)).To(Succeed())
			Eventually(func() bool {
				err := k8sClient.Get(ctx, types.NamespacedName{Namespace: np.ObjectMeta.Namespace, Name: np.ObjectMeta.Name}, awsnp)
				if err != nil {
					return client.IgnoreNotFound(err) == nil
				}
				return false
			}, timeout, interval).Should(BeTrue(), "should be deleted")
		})

		It("Should reconcile GCP networkpeer", func() {
			ctx := context.Background()
			namespace := "gcp-peer-test"
			ns := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: namespace,
				},
			}
			Expect(k8sClient.Create(ctx, ns)).To(Succeed())

			nt := &cloudv1.Network{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "network-" + cloudv1.RandomString(8),
					Namespace: namespace,
					Labels:    map[string]string{},
				},
				Spec: cloudv1.NetworkSpec{
					Provider: "gcp",
					Region:   "us-east-2",
					CIDR:     "10.240.192.0/18",
				},
			}
			nt.ObjectMeta.Labels[cloudv1.ProjectLabel] = cloudv1.RandomString(8)
			Expect(k8sClient.Create(ctx, mockSecretForDataflowAgent(nt))).To(Succeed())
			Expect(k8sClient.Create(ctx, nt)).To(Succeed())
			Eventually(func() error {
				_, err := networkNormal(ctx, nt, true)
				return err
			}, timeout, interval).Should(Succeed(), "wait network ready")
			By("Create gcp networkpeer resource")
			np := &cloudv1.NetworkPeer{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "networkpeer-" + cloudv1.RandomString(8),
					Namespace: namespace,
				},
				Spec: cloudv1.NetworkPeerSpec{
					Source: nt.Name,
					Target: cloudv1.Target{
						GCP: &cloudv1.GCP{
							ProjectID:   "my-peer-123456",
							PeerNetwork: "vpc-123456",
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, np)).To(Succeed())

			Eventually(func() bool {
				if err := k8sClient.Get(ctx, types.NamespacedName{Name: np.ObjectMeta.Name, Namespace: np.ObjectMeta.Namespace}, np); err != nil {
					return false
				}
				return controllerutil.ContainsFinalizer(np, networkpeer.NetworkpeerFinalizer)
			}, timeout, interval).Should(BeTrue(), "should add finalizer to Networkpeer")

			gcpnp := &gcpvpcpeeringv1beta1.Peering{}

			Eventually(func() bool {
				err := k8sClient.Get(ctx, types.NamespacedName{Name: np.ClusterScopeName()}, gcpnp)
				if err != nil {
					return false
				}
				readyCondition := &xpv1.Condition{
					Type:   xpv1.TypeReady,
					Status: corev1.ConditionTrue,
					LastTransitionTime: metav1.Time{
						Time: time.Now(),
					},
				}
				gcpnp.Status.ResourceStatus.ConditionedStatus.Conditions = append(gcpnp.Status.ResourceStatus.ConditionedStatus.Conditions, *readyCondition)
				return (k8sClient.Status().Update(ctx, gcpnp) == nil)
			}, timeout, interval).Should(BeTrue(), "external obj should be ready")

			Eventually(func() bool {
				err := k8sClient.Get(ctx, types.NamespacedName{Namespace: np.ObjectMeta.Namespace, Name: np.ObjectMeta.Name}, np)
				if err != nil {
					return false
				}
				return (np.Status.Phase == cloudv1.NetworkPeerPhaseActive)
			}, timeout, interval).Should(BeTrue(), "should be Available phase")

			By("Delete gcp networkpeer resource")
			Expect(k8sClient.Delete(ctx, np)).To(Succeed())
			Eventually(func() bool {
				err := k8sClient.Get(ctx, types.NamespacedName{Namespace: np.ObjectMeta.Namespace, Name: np.ObjectMeta.Name}, gcpnp)
				if err != nil {
					return client.IgnoreNotFound(err) == nil
				}
				return false
			}, timeout, interval).Should(BeTrue(), "should be deleted")
		})
	})
})
