/*
 *
 * Copyright the Velero contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * /
 */

// Refer to https://github.com/vmware-tanzu/velero/issues/4253
package backups

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

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

	. "github.com/vmware-tanzu/velero/test"
	. "github.com/vmware-tanzu/velero/test/util/k8s"
	. "github.com/vmware-tanzu/velero/test/util/providers"
	. "github.com/vmware-tanzu/velero/test/util/velero"
)

type SyncBackups struct {
	testNS     string
	backupName string
}

func (b *SyncBackups) Init() {
	rand.Seed(time.Now().UnixNano())
	UUIDgen, _ = uuid.NewRandom()
	b.testNS = "sync-bsl-test-" + UUIDgen.String()
	b.backupName = "sync-bsl-test-" + UUIDgen.String()
}

func BackupsSyncTest() {
	test := new(SyncBackups)
	var (
		err error
	)
	veleroCfg := VeleroCfg
	BeforeEach(func() {
		flag.Parse()
		if InstallVelero {
			veleroCfg.UseVolumeSnapshots = false
			Expect(VeleroInstall(context.Background(), &veleroCfg, false)).To(Succeed())
		}
	})

	AfterEach(func() {
		if CurrentSpecReport().Failed() && veleroCfg.FailFast {
			fmt.Println("Test case failed and fail fast is enabled. Skip resource clean up.")
		} else {
			By("Clean backups after test", func() {
				DeleteAllBackups(context.Background(), &veleroCfg)
			})
			if InstallVelero {
				ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5)
				defer ctxCancel()
				Expect(VeleroUninstall(ctx, veleroCfg)).To(Succeed())
			}
		}
	})

	It("Backups in object storage should be synced to a new Velero successfully", func() {
		test.Init()
		ctx, ctxCancel := context.WithTimeout(context.Background(), 30*time.Minute)
		defer ctxCancel()
		By(fmt.Sprintf("Prepare workload as target to backup by creating namespace %s namespace", test.testNS))
		Expect(CreateNamespace(ctx, *veleroCfg.ClientToInstallVelero, test.testNS)).To(Succeed(),
			fmt.Sprintf("Failed to create %s namespace", test.testNS))

		if CurrentSpecReport().Failed() && veleroCfg.FailFast {
			fmt.Println("Test case failed and fail fast is enabled. Skip resource clean up.")
		} else {
			defer func() {
				Expect(DeleteNamespace(ctx, *veleroCfg.ClientToInstallVelero, test.testNS, false)).To(Succeed(), fmt.Sprintf("Failed to delete the namespace %s", test.testNS))
			}()
		}

		var BackupCfg BackupConfig
		BackupCfg.BackupName = test.backupName
		BackupCfg.Namespace = test.testNS
		BackupCfg.BackupLocation = ""
		BackupCfg.UseVolumeSnapshots = false
		BackupCfg.Selector = ""
		By(fmt.Sprintf("Backup the workload in %s namespace", test.testNS), func() {
			Expect(VeleroBackupNamespace(ctx, veleroCfg.VeleroCLI,
				veleroCfg.VeleroNamespace, BackupCfg)).To(Succeed(), func() string {
				RunDebug(context.Background(), veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace, test.backupName, "")
				return "Fail to backup workload"
			})
		})

		By("Uninstall velero", func() {
			Expect(VeleroUninstall(ctx, veleroCfg)).To(Succeed())
		})

		By("Install velero", func() {
			veleroCfg := VeleroCfg
			veleroCfg.UseVolumeSnapshots = false
			Expect(VeleroInstall(ctx, &veleroCfg, false)).To(Succeed())
		})

		By("Check all backups in object storage are synced to Velero", func() {
			Expect(test.IsBackupsSynced(ctx, &veleroCfg, ctxCancel)).To(Succeed(), fmt.Sprintf("Failed to sync backup %s from object storage", test.backupName))
		})
	})

	It("Deleted backups in object storage are synced to be deleted in Velero", func() {
		test.Init()
		ctx, ctxCancel := context.WithTimeout(context.Background(), 30*time.Minute)
		defer ctxCancel()
		By(fmt.Sprintf("Prepare workload as target to backup by creating namespace in %s namespace", test.testNS), func() {
			Expect(CreateNamespace(ctx, *veleroCfg.ClientToInstallVelero, test.testNS)).To(Succeed(),
				fmt.Sprintf("Failed to create %s namespace", test.testNS))
		})

		if !CurrentSpecReport().Failed() || !veleroCfg.FailFast {
			defer func() {
				Expect(DeleteNamespace(ctx, *veleroCfg.ClientToInstallVelero, test.testNS, false)).To(Succeed(),
					fmt.Sprintf("Failed to delete the namespace %s", test.testNS))
			}()
		} else {
			fmt.Println("Test case failed and fail fast is enabled. Skip resource clean up.")
		}

		var BackupCfg BackupConfig
		BackupCfg.BackupName = test.backupName
		BackupCfg.Namespace = test.testNS
		BackupCfg.BackupLocation = ""
		BackupCfg.UseVolumeSnapshots = false
		BackupCfg.Selector = ""
		By(fmt.Sprintf("Backup the workload in %s namespace", test.testNS), func() {
			Expect(VeleroBackupNamespace(ctx, veleroCfg.VeleroCLI,
				veleroCfg.VeleroNamespace, BackupCfg)).To(Succeed(), func() string {
				RunDebug(context.Background(), veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace, test.backupName, "")
				return "Fail to backup workload"
			})
		})

		By(fmt.Sprintf("Delete %s backup files in object store", test.backupName), func() {
			err = DeleteObjectsInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket,
				veleroCfg.BSLPrefix, veleroCfg.BSLConfig, test.backupName, BackupObjectsPrefix)
			Expect(err).To(Succeed(), fmt.Sprintf("Failed to delete object in bucket %s with err %v", test.backupName, err))
		})

		By(fmt.Sprintf("Check %s backup files in object store is deleted", test.backupName), func() {
			err = ObjectsShouldNotBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket,
				veleroCfg.BSLPrefix, veleroCfg.BSLConfig, test.backupName, BackupObjectsPrefix, 1)
			Expect(err).To(Succeed(), fmt.Sprintf("Failed to delete object in bucket %s with err %v", test.backupName, err))
		})

		By("Check if backups are deleted as a result of sync from BSL", func() {
			Expect(WaitBackupDeleted(ctx, test.backupName, time.Minute*10, &veleroCfg)).To(Succeed(), fmt.Sprintf("Failed to check backup %s deleted", test.backupName))
		})
	})
}

func (b *SyncBackups) IsBackupsSynced(ctx context.Context, veleroCfg *VeleroConfig, ctxCancel context.CancelFunc) error {
	defer ctxCancel()
	return WaitForBackupToBeCreated(ctx, b.backupName, 10*time.Minute, veleroCfg)
}
