package schedule

import (
	"context"
	"fmt"
	"strings"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"k8s.io/apimachinery/pkg/util/wait"

	velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
	framework "github.com/vmware-tanzu/velero/test/e2e/test"
	k8sutil "github.com/vmware-tanzu/velero/test/util/k8s"
	veleroutil "github.com/vmware-tanzu/velero/test/util/velero"
)

type PeriodicalCase struct {
	framework.TestCase
	ScheduleName string
	ScheduleArgs []string
	Period       int // The minimum unit is minute.
}

var SchedulePeriodicalTest func() = framework.TestFunc(&PeriodicalCase{})

func (n *PeriodicalCase) Init() error {
	Expect(n.TestCase.Init()).To(Succeed())

	n.CaseBaseName = "schedule-backup-" + n.UUIDgen
	n.NSIncluded = &[]string{n.CaseBaseName}
	n.ScheduleName = "schedule-" + n.CaseBaseName
	n.RestoreName = "restore-" + n.CaseBaseName
	n.TestMsg = &framework.TestMSG{
		Desc:      "Set up a scheduled backup defined by a Cron expression",
		FailedMSG: "Failed to schedule a backup",
		Text:      "Should backup periodically according to the schedule",
	}
	n.ScheduleArgs = []string{
		"--include-namespaces", strings.Join(*n.NSIncluded, ","),
		"--schedule=@every 1m",
	}

	return nil
}

func (n *PeriodicalCase) CreateResources() error {
	for _, ns := range *n.NSIncluded {
		By(fmt.Sprintf("Creating namespaces %s ......\n", ns), func() {
			Expect(
				k8sutil.CreateNamespace(
					n.Ctx,
					n.Client,
					ns,
				),
			).To(
				Succeed(),
				fmt.Sprintf("Failed to create namespace %s", ns),
			)
		})

		cmName := n.CaseBaseName
		fmt.Printf("Creating ConfigMap %s in namespaces ...%s\n", cmName, ns)
		_, err := k8sutil.CreateConfigMap(
			n.Client.ClientGo,
			ns,
			cmName,
			nil,
			nil,
		)
		Expect(err).To(Succeed(), fmt.Sprintf("failed to create ConfigMap in the namespace %q", ns))
	}
	return nil
}

func (n *PeriodicalCase) Backup() error {
	By(fmt.Sprintf("Creating schedule %s ......\n", n.ScheduleName), func() {
		Expect(
			veleroutil.VeleroScheduleCreate(
				n.Ctx,
				n.VeleroCfg.VeleroCLI,
				n.VeleroCfg.VeleroNamespace,
				n.ScheduleName,
				n.ScheduleArgs,
			),
		).To(Succeed())
	})

	By(fmt.Sprintf("No immediate backup is created by schedule %s\n", n.ScheduleName), func() {
		backups, err := veleroutil.GetBackupsForSchedule(
			n.Ctx,
			n.Client.Kubebuilder,
			n.ScheduleName,
			n.VeleroCfg.Namespace,
		)
		Expect(err).To(Succeed())
		Expect(backups).To(BeEmpty())
	})

	By("Wait until schedule triggers backup.", func() {
		err := wait.PollUntilContextTimeout(
			n.Ctx,
			30*time.Second,
			5*time.Minute,
			true,
			func(ctx context.Context) (bool, error) {
				backups, err := veleroutil.GetBackupsForSchedule(
					n.Ctx,
					n.Client.Kubebuilder,
					n.ScheduleName,
					n.VeleroCfg.Namespace,
				)
				if err != nil {
					fmt.Println("Fail to get backups for schedule.")
					return false, err
				}

				// The triggered backup completed.
				if len(backups) == 1 &&
					backups[0].Status.Phase == velerov1api.BackupPhaseCompleted {
					n.BackupName = backups[0].Name
					return true, nil
				}

				return false, nil
			},
		)

		Expect(err).To(Succeed())
	})

	n.RestoreArgs = []string{
		"create", "--namespace", n.VeleroCfg.VeleroNamespace, "restore", n.RestoreName,
		"--from-backup", n.BackupName,
		"--wait",
	}

	By(fmt.Sprintf("Pause schedule %s ......\n", n.ScheduleName), func() {
		Expect(
			veleroutil.VeleroSchedulePause(
				n.Ctx,
				n.VeleroCfg.VeleroCLI,
				n.VeleroCfg.VeleroNamespace,
				n.ScheduleName,
			),
		).To(Succeed())
	})

	By(("Sleep 2 minutes"), func() {
		time.Sleep(2 * time.Minute)
	})

	backups, err := veleroutil.GetBackupsForSchedule(
		n.Ctx,
		n.Client.Kubebuilder,
		n.ScheduleName,
		n.VeleroCfg.Namespace,
	)
	Expect(err).To(Succeed(), fmt.Sprintf("Fail to get backups from schedule %s", n.ScheduleName))

	backupCountPostPause := len(backups)
	fmt.Printf("After pause, backups count is %d\n", backupCountPostPause)

	By(fmt.Sprintf("Verify no new backups from %s ......\n", n.ScheduleName), func() {
		Expect(backupCountPostPause).To(Equal(1))
	})

	By(fmt.Sprintf("Unpause schedule %s ......\n", n.ScheduleName), func() {
		Expect(
			veleroutil.VeleroScheduleUnpause(
				n.Ctx,
				n.VeleroCfg.VeleroCLI,
				n.VeleroCfg.VeleroNamespace,
				n.ScheduleName,
			),
		).To(Succeed())
	})

	return nil
}

func (n *PeriodicalCase) Verify() error {
	By("Namespaces were restored", func() {
		for _, ns := range *n.NSIncluded {
			_, err := k8sutil.GetConfigMap(n.Client.ClientGo, ns, n.CaseBaseName)
			Expect(err).ShouldNot(HaveOccurred(), fmt.Sprintf("failed to list CM in namespace: %s\n", ns))
		}
	})
	return nil
}

func (n *PeriodicalCase) Clean() error {
	if CurrentSpecReport().Failed() && n.VeleroCfg.FailFast {
		fmt.Println("Test case failed and fail fast is enabled. Skip resource clean up.")
	} else {
		Expect(
			veleroutil.VeleroScheduleDelete(
				n.Ctx,
				n.VeleroCfg.VeleroCLI,
				n.VeleroCfg.VeleroNamespace,
				n.ScheduleName,
			),
		).To(Succeed())

		Expect(n.TestCase.Clean()).To(Succeed())
	}

	return nil
}
