package integration

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

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	"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("Restore 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 = "restore-test"
	)

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

			nt := newNetwork("us-east-2")
			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 := newSampleCluster(nt.Name)
			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")

			b := &cloudv1.Backup{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "backup" + cloudv1.RandomString(8),
					Namespace: "default",
				},
				Spec: cloudv1.BackupSpec{
					ClusterName: c.ObjectMeta.Name,
				},
			}
			Expect(k8sClient.Create(ctx, b)).To(Succeed())

			r := &cloudv1.Restore{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "restore" + cloudv1.RandomString(8),
					Namespace: "default",
				},
				Spec: cloudv1.RestoreSpec{
					ClusterName: c.ObjectMeta.Name,
					From: cloudv1.From{
						Backup: b.ObjectMeta.Name,
					},
				},
			}
			Expect(k8sClient.Create(ctx, r)).To(Succeed())

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

			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 restore spec?
			Eventually(func() bool {
				var tidbRestore v1alpha1.Restore
				return shootClient.Get(ctx, types.NamespacedName{Name: r.ObjectMeta.Name, Namespace: clusterStatus.Kubernetes.Namespace}, &tidbRestore) == nil
			}, timeout, interval).Should(BeTrue(), "should create an restore resource for AWS Restore")

			By("Delete restore resource")
			Expect(k8sClient.Delete(ctx, r)).To(Succeed())
			// TODO: assert the shoot restore resources is cleaned

			By("Create a network in other region")
			nt2 := newNetwork("test-region")
			Expect(k8sClient.Create(ctx, nt2)).To(Succeed())
			Eventually(func() error {
				var err error
				_, err = networkNormal(ctx, nt2, true)
				return err
			}, timeout, interval).Should(Succeed())
			c2 := newSampleCluster(nt2.Name)
			Expect(k8sClient.Create(ctx, c2)).To(Succeed())

			crossReionRestore := &cloudv1.Restore{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "cross-region-restore-" + cloudv1.RandomString(8),
					Namespace: "default",
				},
				Spec: cloudv1.RestoreSpec{
					ClusterName: c2.ObjectMeta.Name,
					From: cloudv1.From{
						Backup: b.ObjectMeta.Name,
					},
				},
			}
			Expect(k8sClient.Create(ctx, crossReionRestore)).To(Succeed())
			Eventually(func() bool {
				var tidbRestore v1alpha1.Restore
				err := shootClient.Get(ctx, types.NamespacedName{Name: crossReionRestore.ObjectMeta.Name, Namespace: fmt.Sprintf("tidb%s", c2.Name)}, &tidbRestore)
				if err != nil {
					return false
				}
				// region should be same with bucket region
				if tidbRestore.Spec.S3.Region == "us-east-2" {
					return true
				}
				return false
			}, timeout, interval).Should(BeTrue(), "should create an restore resource in shoot cluster")
		})
	})
})

func newNetwork(region string) *cloudv1.Network {
	return &cloudv1.Network{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "network-" + cloudv1.RandomString(8),
			Namespace: "default",
			Labels: map[string]string{
				cloudv1.ProjectLabel: cloudv1.RandomString(5),
			},
		},
		Spec: cloudv1.NetworkSpec{
			Provider: "aws",
			Region:   region,
			CIDR:     "10.251.0.0/16",
		},
	}
}

func newSampleCluster(networkName string) *cloudv1.Cluster {
	return &cloudv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("%d", rand.Int()),
			Namespace: "default",
		},
		Spec: cloudv1.ClusterSpec{
			NetworkName: networkName,
			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),
				},
			},
		},
	}
}
