package e2e

import (
	"fmt"
	"net"

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

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"k8s.io/apimachinery/pkg/types"

	"time"

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

var _ = Describe("aws unusual scene test", func() {
	interval := 10 * time.Second

	var (
		Cluster      *cloudv1.Cluster
		Region       string
		InstanceType string
	)

	BeforeEach(func() {
		Region = e2eEnv.Config.AWS.Region
		InstanceType = e2eEnv.Config.AWS.InstanceType

		By("create correct cluster")
		Cluster = newCommonCluster(e2eEnv.AwsNetwork.Name, InstanceType)
		Expect(infraCli.Create(ctx, Cluster)).Should(Succeed())
		waitClusterReady(ctx, Cluster, 25*time.Minute, interval)
	})

	AfterEach(func() {
		By("delete correct cluster")
		Expect(infraCli.Delete(ctx, Cluster)).Should(Succeed())
		waitClusterDeleted(ctx, Cluster, 15*time.Minute, interval)
	})

	It("Connect cluster use a specific IP whitelist", func() {
		By("update cluster use 183.209.115.85/32 IP whitelist")
		Expect(infraCli.Get(ctx, types.NamespacedName{Namespace: Cluster.Namespace, Name: Cluster.Name}, Cluster)).Should(Succeed())
		Cluster.Spec.SqlEndpoints.Public.LoadBalancerSourceRanges = []string{"183.209.115.85/32"}
		Expect(infraCli.Update(ctx, Cluster)).Should(Succeed())

		By("do cluster connect")
		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
			}
			_, err = net.Dial("tcp", fmt.Sprintf("%s:4000", Cluster.Status.SqlEndpoints.PublicEndpoint.Host))
			Expect(err).NotTo(BeNil())
			_, ok := err.(*net.OpError)
			Expect(ok).Should(BeTrue())
			return nil
		}, 10*time.Minute, interval).Should(Succeed(), "connect should fail")

		By("update cluster use 0.0.0.0/0 IP whitelist")
		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
			}
			Cluster.Spec.SqlEndpoints.Public.LoadBalancerSourceRanges = []string{"0.0.0.0/0"}
			err = infraCli.Update(ctx, Cluster)
			if err != nil {
				logger.Errorw("update cluster error", "cluster", Cluster.Name, "error", err.Error())
				return errWait
			}
			return nil
		}, 10*time.Minute, interval).Should(Succeed(), "update cluster IP whitelist failed")

		By("do cluster connect")
		waitTiDBReady(ctx, Cluster, 25*time.Minute, interval)
	})

	Describe("Import data which is incorrect", func() {
		It("Import cluster doesn't exist", func() {
			Import := newImportFromS3("e2e", cloudv1.DataFormatSQL, e2eEnv.Config.AWS.ImportSQLS3URL, Region)
			err := infraCli.Create(ctx, Import)
			Expect(err).NotTo(BeNil())
			statusErr, ok := err.(*errors.StatusError)
			Expect(ok).Should(BeTrue())
			Expect(statusErr.ErrStatus.Status).Should(Equal("Failure"))
			Expect(statusErr.ErrStatus.Reason).Should(Equal(metav1.StatusReasonInvalid))
		})
		It("Has no S3 policy ", func() {
			Import := newImportFromS3(Cluster.Name, cloudv1.DataFormatSQL, e2eEnv.Config.AWS.ImportNoPlicyS3URL, Region)
			Expect(infraCli.Create(ctx, Import)).Should(Succeed())
			Eventually(func() error {
				err := infraCli.Get(ctx, types.NamespacedName{Namespace: Import.Namespace, Name: Import.Name}, Import)
				if err != nil {
					logger.Errorw("error inspecting import status", "error", err.Error())
					return err
				}
				if Import.Status.Phase == cloudv1.ImportCompleted {
					logger.Errorw("check no S3 policy failed!")
					return err
				}
				if Import.Status.Phase != cloudv1.ImportFailed {
					logger.Infow("wait import ready", "current phase", Import.Status.Phase)
					return errWait
				}
				return nil
			}, 15*time.Minute, interval).Should(Succeed(), "Import should be ready in 15 minutes")

			By("delete import")
			Expect(infraCli.Delete(ctx, Import)).Should(Succeed())
			waitImportDeleted(ctx, Import, 15*time.Minute, interval)
		})

		It("The data format is wrong", func() {
			Import := newImportFromS3(Cluster.Name, cloudv1.DataFormatAuroraParquet, e2eEnv.Config.AWS.ImportSQLS3URL, Region)
			Expect(infraCli.Create(ctx, Import)).Should(Succeed())
			Eventually(func() error {
				err := infraCli.Get(ctx, types.NamespacedName{Namespace: Import.Namespace, Name: Import.Name}, Import)
				if err != nil {
					logger.Errorw("error inspecting import status", "error", err.Error())
					return err
				}
				if Import.Status.Phase != cloudv1.ImportCompleted {
					logger.Infow("wait import ready", "current phase", Import.Status.Phase)
					return errWait
				}
				Expect(Import.Status.TotalSize).Should(Equal(uint64(0)))
				Expect(*Import.Status.TotalFiles).Should(Equal(uint32(0)))
				return nil
			}, 15*time.Minute, interval).Should(Succeed(), "Import should be ready in 15 minutes")

			By("delete import")
			Expect(infraCli.Delete(ctx, Import)).Should(Succeed())
			waitImportDeleted(ctx, Import, 15*time.Minute, interval)
		})

		It("Import region is different with cluster region", func() {
			Import := newImportFromS3(Cluster.Name, cloudv1.DataFormatSQL, e2eEnv.Config.AWS.ImportAnotherRegionS3URL, e2eEnv.Config.AWS.AnotherRegion)
			Expect(infraCli.Create(ctx, Import)).Should(Succeed())
			waitImportReady(ctx, Import, 15*time.Minute, interval)

			By("check import data")
			Expect(Import.Status.CompletedTables).Should(Equal(uint32(2)))
			Expect(Import.Status.PendingTables).Should(Equal(uint32(0)))

			By("delete import")
			Expect(infraCli.Delete(ctx, Import)).Should(Succeed())
			waitImportDeleted(ctx, Import, 15*time.Minute, interval)
		})
	})

	It("Backup cluster doesn't exist", func() {
		Backup := newCommonBackup("e2e")
		err := infraCli.Create(ctx, Backup)
		Expect(err).NotTo(BeNil())
		statusErr, ok := err.(*errors.StatusError)
		Expect(ok).Should(BeTrue())
		Expect(statusErr.ErrStatus.Status).Should(Equal("Failure"))
		Expect(statusErr.ErrStatus.Reason).Should(Equal(metav1.StatusReasonInvalid))
	})

	Describe("Restore cluster which is incorrect", func() {
		It("Restore cluster which is incorrect", func() {
			By("do correct backup")
			Backup := newCommonBackup(Cluster.Name)
			Expect(infraCli.Create(ctx, Backup)).Should(Succeed())
			waitBackupReady(ctx, Backup, 15*time.Minute, interval)

			By("do restore,but the restore cluster doesn't exit")
			Restore := newCommonRestore(Cluster.Name, "e2e", Backup.Name)
			err := infraCli.Create(ctx, Restore)
			Expect(err).NotTo(BeNil())
			statusErr, ok := err.(*errors.StatusError)
			Expect(ok).Should(BeTrue())
			Expect(statusErr.ErrStatus.Status).Should(Equal("Failure"))
			Expect(statusErr.ErrStatus.Reason).Should(Equal(metav1.StatusReasonInvalid))

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

		It("The restore backup name doesn't exit", func() {
			By("create br cluster")
			brCluster := newCommonCluster(e2eEnv.AwsNetwork.Name, InstanceType)
			Expect(infraCli.Create(ctx, brCluster)).Should(Succeed())
			waitClusterReady(ctx, brCluster, 25*time.Minute, interval)

			Restore := newCommonRestore(Cluster.Name, brCluster.Name, "e2e")
			err := infraCli.Create(ctx, Restore)
			Expect(err).NotTo(BeNil())
			statusErr, ok := err.(*errors.StatusError)
			Expect(ok).Should(BeTrue())
			Expect(statusErr.ErrStatus.Status).Should(Equal("Failure"))
			Expect(statusErr.ErrStatus.Reason).Should(Equal(metav1.StatusReasonInvalid))

			By("delete restore cluster")
			Expect(infraCli.Delete(ctx, brCluster)).Should(Succeed())
			waitClusterDeleted(ctx, brCluster, 5*time.Minute, interval)
		})
	})
})
