package integration

import (
	"context"
	"fmt"
	"math/rand"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	kerrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"

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

var _ = Describe("Backup controller", func() {
	BeforeEach(func() {
	})
	AfterEach(func() {
	})

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

	Context("When reconcile backup", func() {
		It("Should reconcile AWS backup", func() {
			ctx := context.Background()
			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:     "10.251.0.0/16",
				},
			}
			Expect(k8sClient.Create(ctx, nt)).To(Succeed())
			var networkStatus *cloudv1.GardenerNetworkStatus
			Eventually(func() error {
				var err error
				networkStatus, err = networkNormal(ctx, nt, true)
				return err
			}, timeout, interval).Should(Succeed())

			c := &cloudv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name:      fmt.Sprintf("%d", rand.Int()),
					Namespace: namespace,
				},
				Spec: cloudv1.ClusterSpec{
					NetworkName: nt.Name,
					Cluster: cloudv1.ClusterTopology{
						TiDB: cloudv1.TiDB{
							Replicas:     2,
							InstanceType: "test",
						},
						TiKV: cloudv1.TiKV{
							Replicas:     3,
							InstanceType: "test",
							StorageSize:  resource.MustParse("100Gi"),
						},
						PD: cloudv1.PD{
							Replicas:     3,
							InstanceType: "test",
							StorageSize:  resource.MustParse("1Gi"),
						},
					},
					SqlEndpoints: cloudv1.SqlEndpoints{
						Public: cloudv1.Service{
							Enabled: pointer.Bool(true),
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, c)).To(Succeed())
			var clusterStatus *cloudv1.AwsClusterStatus
			Eventually(func() bool {
				err := k8sClient.Get(ctx, types.NamespacedName{Namespace: c.Namespace, Name: c.Name}, c)
				if err != nil {
					return false
				}
				clusterStatus, err = cloudv1.AwsClusterStatusFromCluster(c)
				if err != nil {
					return false
				}
				return c.Status.Phase == cloudv1.ClusterPhaseRunning
			}, timeout, interval).Should(BeTrue(), "cluster phase should be running")
			By("Create backup resource")
			b := &cloudv1.Backup{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "backup" + cloudv1.RandomString(8),
					Namespace: namespace,
				},
				Spec: cloudv1.BackupSpec{
					ClusterName: c.ObjectMeta.Name,
				},
			}
			Expect(k8sClient.Create(ctx, b)).To(Succeed())

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

			var shootClient client.Client
			Eventually(func() bool {
				shootCtx, err := gardenerEnv.GetShootContext(ctx, networkStatus.ShootName)
				if err != nil {
					return false
				}
				shootClient = shootCtx.Manager.GetClient()
				return true
			}, timeout, interval).Should(BeTrue(), "should get shoot client")

			// TODO: assert the backup spec?
			Eventually(func() bool {
				var tidbBackup v1alpha1.Backup
				return shootClient.Get(ctx, types.NamespacedName{Name: b.ObjectMeta.Name, Namespace: clusterStatus.Kubernetes.Namespace}, &tidbBackup) == nil
			}, timeout, interval).Should(BeTrue(), "should create an backup resource for AWS Backup")

			// TODO: what if a backup is being deleted when there is an ongoing restore on the backup?
			By("Delete backup resource")
			Expect(k8sClient.Delete(ctx, b)).To(Succeed())
			Eventually(func() bool {
				err := k8sClient.Get(ctx, types.NamespacedName{Name: b.ObjectMeta.Name, Namespace: b.ObjectMeta.Namespace}, b)
				return kerrors.IsNotFound(err)
			}, timeout, interval).Should(BeTrue(), "should delete backup")
			// TODO: assert the cluster CR in shoot ns is cleaned

			By("Create backup again")
			b2 := &cloudv1.Backup{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "backup" + cloudv1.RandomString(8),
					Namespace: namespace,
				},
				Spec: cloudv1.BackupSpec{
					ClusterName: c.ObjectMeta.Name,
				},
			}
			Expect(k8sClient.Create(ctx, b2)).To(Succeed())
			Eventually(func() bool {
				if err := k8sClient.Get(ctx, types.NamespacedName{Name: b2.ObjectMeta.Name, Namespace: b2.ObjectMeta.Namespace}, b2); err != nil {
					return false
				}
				return controllerutil.ContainsFinalizer(b2, backup.BackupFinalizer)
			}, timeout, interval).Should(BeTrue(), "should add finalizer to Backup")

			By("Delete cluster should be blocked by the preparing backup")
			Expect(k8sClient.Delete(ctx, c)).To(Succeed())
			Consistently(func() error {
				return k8sClient.Get(ctx, types.NamespacedName{Name: c.ObjectMeta.Name, Namespace: c.ObjectMeta.Namespace}, c)
			}, 10*interval, interval).Should(Succeed(), "should delete cluster")
		})
	})
})
