package e2e

import (
	"fmt"
	"time"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"

	"github.com/tidbcloud/infra-api/pkg/consts"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
)

var _ = Describe("gcp normal scene full link test", func() {
	interval := 10 * time.Second
	privoderName := consts.ProviderTypeGCP

	var (
		NetworkPeer  *cloudv1.NetworkPeer
		Cluster      *cloudv1.Cluster
		Capability   cloudv1.Capability
		InstanceType string
	)

	BeforeEach(func() {
		InstanceType = e2eEnv.Config.GCP.InstanceType

		By("get provider type")
		ProviderType := newProviderType(privoderName)
		Expect(infraCli.Get(ctx, types.NamespacedName{Namespace: ProviderType.Namespace, Name: ProviderType.Name}, ProviderType)).Should(Succeed())
		for _, capability := range ProviderType.Spec.Capabilities {
			if capability.Group == "cloud.pingcap.com" {
				Capability = capability
				break
			}
		}

		By("create cluster")
		if !IN(Capability.Resources, "Cluster") {
			Skip(privoderName + " not support Cluster")
		}
		if e2eEnv.GcpNetwork != nil && e2eEnv.GcpNetwork.Status.Phase == cloudv1.NetworkPhaseNormal {
			Cluster = newCommonCluster(e2eEnv.GcpNetwork.Name, InstanceType)
			Expect(infraCli.Create(ctx, Cluster)).Should(Succeed())
			waitClusterReady(ctx, Cluster, 25*time.Minute, interval)
			Eventually(func() error {
				err := infraCli.Get(ctx, types.NamespacedName{Namespace: Cluster.Namespace, Name: Cluster.Name}, Cluster)
				if err != nil {
					logger.Errorw("error inspecting cluster status", "cluster", Cluster.Name, "error", err.Error())
					return err
				}
				if Cluster.Status.DB == nil ||
					Cluster.Status.DB.TiDB.Status != cloudv1.ComponentNormal ||
					Cluster.Status.DB.TiKV.Status != cloudv1.ComponentNormal ||
					Cluster.Status.DB.TiFlash.Status != cloudv1.ComponentNormal ||
					Cluster.Status.DB.PD.Status != cloudv1.ComponentNormal {
					logger.Infow("wait cluster DB ready")
					return errWait
				}
				return nil
			}, 5*time.Minute, interval).Should(Succeed(), "Cluster DB status should be ready in 5 minutes")
		}
	})

	AfterEach(func() {
		By("delete cluster")
		if !IN(Capability.Resources, "Cluster") {
			Skip(privoderName + " not support Cluster")
		}
		if Cluster != nil {
			Expect(infraCli.Delete(ctx, Cluster)).Should(Succeed())
			waitClusterDeleted(ctx, Cluster, 15*time.Minute, interval)
		}
	})

	It("Should get provider successfully", func() {
		provider := newProvider(privoderName)
		Expect(infraCli.Get(ctx, types.NamespacedName{Namespace: provider.Namespace, Name: provider.Name}, provider)).Should(Succeed())
		Expect(len(provider.Spec.Regions) > 0).Should(BeTrue())
		Expect(len(provider.Spec.InstanceTypes) > 0).Should(BeTrue())
	})

	It("Should create network peering successfully", func() {
		if !IN(Capability.Resources, "NetworkPeer") {
			Skip(privoderName + " not support NetworkPeer")
		}
		if e2eEnv.GcpNetwork == nil || e2eEnv.GcpNetwork.Status.Phase != cloudv1.NetworkPhaseNormal {
			Skip("network not ready,not do network peering")
		}

		By("create network peering")
		NetworkPeer = &cloudv1.NetworkPeer{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: e2eEnv.Namespace,
				Name:      fmt.Sprintf("e2e-peer-%s", cloudv1.RandomString(6)),
				Labels:    e2eResourceLabels(),
			},
			Spec: cloudv1.NetworkPeerSpec{
				Source: e2eEnv.GcpNetwork.Name,
				Target: cloudv1.Target{
					GCP: &cloudv1.GCP{
						ProjectID:   e2eEnv.Config.GCP.ProjectID,
						PeerNetwork: e2eEnv.Config.GCP.NetworkName,
					},
				},
			},
		}
		Expect(infraCli.Create(ctx, NetworkPeer)).Should(Succeed())
		waitGCPNetworkPeerReady(ctx, NetworkPeer, 5*time.Minute, interval)

		By("delete network peering")
		Expect(infraCli.Delete(ctx, NetworkPeer)).Should(Succeed())
		waitNetworkPeerDeleted(ctx, NetworkPeer, 5*time.Minute, interval)
	})

	It("Should connect cluster successfully", func() {
		if !IN(Capability.Resources, "Cluster") {
			Skip(privoderName + " not support Cluster")
		}
		if Cluster == nil || Cluster.Status.Phase != cloudv1.ClusterPhaseRunning {
			Skip("cluster not ready, not do connect")
		}

		By("public endpoint connect")
		waitTiDBReady(ctx, Cluster, 20*time.Minute, interval)
	})

	It("Should backup&restore successfully", func() {
		if !IN(Capability.Resources, "Backup") {
			Skip(privoderName + " not support backup")
		}
		if Cluster == nil || Cluster.Status.Phase != cloudv1.ClusterPhaseRunning {
			Skip("cluster not ready, not do backup")
		}
		By("do backup")
		Backup := newCommonBackup(Cluster.Name)
		Expect(infraCli.Create(ctx, Backup)).Should(Succeed())
		waitBackupReady(ctx, Backup, 15*time.Minute, interval)

		if !IN(Capability.Resources, "Restore") {
			Skip(privoderName + " not support Restore")
		}
		if Backup.Status.Phase != cloudv1.BackupComplete {
			Skip("backup is not ready,not do restore")
		}

		By("do restore")
		RestoreInSameRegion(e2eEnv.GcpNetwork.Name, Cluster.Name, Backup.Name, InstanceType, interval)

		//gcp not support secondary in dev

		By("delete backup")
		Expect(infraCli.Delete(ctx, Backup)).Should(Succeed())
		waitBackupDeleted(ctx, Backup, 10*time.Minute, interval)

	})

	It("Shoud scale out cluster successfylly", func() {
		if !IN(Capability.Resources, "Cluster") {
			Skip(privoderName + " not support Cluster")
		}
		if Cluster == nil || Cluster.Status.Phase != cloudv1.ClusterPhaseRunning {
			Skip("cluster not ready, not do scale")
		}
		err := infraCli.Get(ctx, types.NamespacedName{Namespace: Cluster.Namespace, Name: Cluster.Name}, Cluster)
		Expect(err).ShouldNot(HaveOccurred())

		Cluster.Spec.Cluster.TiDB.Replicas = 3
		Cluster.Spec.Cluster.TiKV.Replicas = 6
		Cluster.Spec.Cluster.PD.Replicas = 3
		Cluster.Spec.Cluster.TiFlash.Replicas = 2
		Expect(infraCli.Update(ctx, Cluster)).Should(Succeed())
		Eventually(func() error {
			err := infraCli.Get(ctx, types.NamespacedName{Namespace: Cluster.Namespace, Name: Cluster.Name}, Cluster)
			if err != nil {
				logger.Errorw("error inspecting scale out cluster status", "error", err.Error())
				return err
			}
			if Cluster.Status.Phase != cloudv1.ClusterPhaseRunning {
				logger.Infow("wait cluster ready", "current phase", Cluster.Status.Phase)
				return errWait
			}
			if Cluster.Status.DB.TiDB.Status != cloudv1.ComponentNormal ||
				Cluster.Status.DB.TiKV.Status != cloudv1.ComponentNormal ||
				Cluster.Status.DB.PD.Status != cloudv1.ComponentNormal ||
				Cluster.Status.DB.TiFlash.Status != cloudv1.ComponentNormal ||
				len(Cluster.Status.DB.TiDB.Members) != 3 ||
				len(Cluster.Status.DB.TiKV.Members) != 6 ||

				len(Cluster.Status.DB.TiFlash.Members) != 2 {
				logger.Infow("scale out cluster status is wrong", "current reason", Cluster.Status.Reason)
				return errWait
			}
			return nil
		}, 15*time.Minute, interval).Should(Succeed(), "Cluster should be ready in 15 minutes")

	})

	It("Should scale in cluster successfully", func() {
		if !IN(Capability.Resources, "Cluster") {
			Skip(privoderName + " not support Cluster")
		}
		if Cluster == nil || Cluster.Status.Phase != cloudv1.ClusterPhaseRunning {
			Skip("cluster not ready, not do scale")
		}
		err := infraCli.Get(ctx, types.NamespacedName{Namespace: Cluster.Namespace, Name: Cluster.Name}, Cluster)
		Expect(err).ShouldNot(HaveOccurred())

		Cluster.Spec.Cluster.TiDB.Replicas = 1
		Cluster.Spec.Cluster.TiKV.Replicas = 3
		Cluster.Spec.Cluster.PD.Replicas = 3
		Cluster.Spec.Cluster.TiFlash.Replicas = 1
		Expect(infraCli.Update(ctx, Cluster)).Should(Succeed())
		Eventually(func() error {
			err := infraCli.Get(ctx, types.NamespacedName{Namespace: Cluster.Namespace, Name: Cluster.Name}, Cluster)
			if err != nil {
				logger.Errorw("error inspecting scale out cluster status", "error", err.Error())
				return err
			}
			if Cluster.Status.Phase != cloudv1.ClusterPhaseRunning {
				logger.Infow("wait cluster ready", "current phase", Cluster.Status.Phase)
				return errWait
			}

			if Cluster.Status.DB.TiDB.Status != cloudv1.ComponentNormal ||
				Cluster.Status.DB.TiKV.Status != cloudv1.ComponentNormal ||
				Cluster.Status.DB.PD.Status != cloudv1.ComponentNormal ||
				Cluster.Status.DB.TiFlash.Status != cloudv1.ComponentNormal ||
				len(Cluster.Status.DB.TiDB.Members) != 1 ||
				len(Cluster.Status.DB.TiKV.Members) != 3 ||

				len(Cluster.Status.DB.TiFlash.Members) != 1 {
				logger.Infow("scale in cluster status is wrong", "current reason", Cluster.Status.Reason)
				return errWait
			}
			return nil
		}, 15*time.Minute, interval).Should(Succeed(), "Cluster should be ready in 15 minutes")
	})
})
