/*
Copyright © contributors to CloudNativePG, established as
CloudNativePG a Series of LF Projects, LLC.

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.

SPDX-License-Identifier: Apache-2.0
*/

package backup

import (
	"fmt"
	"time"

	v1 "github.com/cloudnative-pg/api/pkg/api/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	internalClient "github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/client"
	internalCluster "github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/cluster"
	"github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/command"
	nmsp "github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/namespace"

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

var _ = Describe("Backup and restore", func() {
	var namespace *corev1.Namespace
	var cl client.Client
	BeforeEach(func(ctx SpecContext) {
		var err error
		cl, _, err = internalClient.NewClient()
		Expect(err).NotTo(HaveOccurred())
		namespace, err = nmsp.CreateUniqueNamespace(ctx, cl, "backup-restore")
		Expect(err).NotTo(HaveOccurred())
	})
	AfterEach(func(ctx SpecContext) {
		Expect(cl.Delete(ctx, namespace)).To(Succeed())
	})

	DescribeTable("should backup and restore a cluster",
		func(
			ctx SpecContext,
			factory testCaseFactory,
		) {
			testResources := factory.createBackupRestoreTestResources(namespace.Name)

			By("starting the ObjectStore deployment")
			Expect(testResources.ObjectStoreResources.Create(ctx, cl)).To(Succeed())

			By("creating the ObjectStore")
			Expect(cl.Create(ctx, testResources.ObjectStore)).To(Succeed())

			By("Creating a CloudNativePG cluster")
			src := testResources.SrcCluster
			Expect(cl.Create(ctx, testResources.SrcCluster)).To(Succeed())

			By("Having the Cluster ready")
			Eventually(func(g Gomega) {
				g.Expect(cl.Get(
					ctx,
					types.NamespacedName{
						Name:      src.Name,
						Namespace: src.Namespace,
					},
					src)).To(Succeed())
				g.Expect(internalCluster.IsReady(*src)).To(BeTrue())
			}).WithTimeout(10 * time.Minute).WithPolling(10 * time.Second).Should(Succeed())

			By("Adding data to PostgreSQL")
			clientSet, cfg, err := internalClient.NewClientSet()
			Expect(err).NotTo(HaveOccurred())
			_, _, err = command.ExecuteInContainer(ctx,
				*clientSet,
				cfg,
				command.ContainerLocator{
					NamespaceName: src.Namespace,
					PodName:       fmt.Sprintf("%v-1", src.Name),
					ContainerName: "postgres",
				},
				nil,
				[]string{"psql", "-tAc", "CREATE TABLE test (i int); INSERT INTO test VALUES (1);"})
			Expect(err).NotTo(HaveOccurred())

			By("Creating a backup")
			backup := testResources.SrcBackup
			Expect(cl.Create(ctx, backup)).To(Succeed())

			By("Waiting for the backup to complete")
			Eventually(func(g Gomega) {
				g.Expect(cl.Get(ctx, types.NamespacedName{Name: backup.Name, Namespace: backup.Namespace},
					backup)).To(Succeed())
				g.Expect(backup.Status.Phase).To(BeEquivalentTo(v1.BackupPhaseCompleted))
			}).Within(2 * time.Minute).WithPolling(5 * time.Second).Should(Succeed())

			By("Adding data after the backup")
			_, _, err = command.ExecuteInContainer(ctx,
				*clientSet,
				cfg,
				command.ContainerLocator{
					NamespaceName: src.Namespace,
					PodName:       fmt.Sprintf("%v-1", src.Name),
					ContainerName: "postgres",
				},
				nil,
				[]string{
					"psql", "-tAc",
					"SELECT pg_switch_wal()" +
						"; INSERT INTO test VALUES (2)",
				})
			Expect(err).NotTo(HaveOccurred())
			_, _, err = command.ExecuteInContainer(ctx,
				*clientSet,
				cfg,
				command.ContainerLocator{
					NamespaceName: src.Namespace,
					PodName:       fmt.Sprintf("%v-1", src.Name),
					ContainerName: "postgres",
				},
				nil,
				[]string{
					"psql", "-tAc",
					"SELECT pg_switch_wal()",
				})
			Expect(err).NotTo(HaveOccurred())

			By("Restoring the backup")
			dst := testResources.DstCluster
			Expect(cl.Create(ctx, dst)).To(Succeed())

			By("Having the Cluster ready")
			Eventually(func(g Gomega) {
				g.Expect(cl.Get(ctx,
					types.NamespacedName{Name: dst.Name, Namespace: dst.Namespace},
					dst)).To(Succeed())
				g.Expect(internalCluster.IsReady(*dst)).To(BeTrue())
			}).WithTimeout(10 * time.Minute).WithPolling(10 * time.Second).Should(Succeed())

			By("Verifying the data exists in the restored instance")
			output, _, err := command.ExecuteInContainer(ctx,
				*clientSet,
				cfg,
				command.ContainerLocator{
					NamespaceName: dst.Namespace,
					PodName:       fmt.Sprintf("%v-1", dst.Name),
					ContainerName: "postgres",
				},
				nil,
				[]string{"psql", "-tAc", "SELECT count(*) FROM test;"})
			Expect(err).NotTo(HaveOccurred())
			Expect(output).To(BeEquivalentTo("2\n"))

			By("taking a backup from the restored cluster")
			backup = testResources.DstBackup
			Expect(cl.Create(ctx, backup)).To(Succeed())

			By("Waiting for the backup to complete")
			Eventually(func(g Gomega) {
				g.Expect(cl.Get(ctx, types.NamespacedName{Name: backup.Name, Namespace: backup.Namespace},
					backup)).To(Succeed())
				g.Expect(backup.Status.Phase).To(BeEquivalentTo(v1.BackupPhaseCompleted))
			}).Within(3 * time.Minute).WithPolling(5 * time.Second).Should(Succeed())
		},
		Entry(
			"using the plugin for backup and restore on S3",
			&s3BackupPluginBackupPluginRestore{},
		),
		Entry(
			"using the plugin for backup and in-tree for restore on S3",
			&s3BackupPluginBackupInTreeRestore{},
		),
		Entry(
			"using in-tree for backup and the plugin for restore on S3",
			&s3BackupPluginInTreeBackupPluginRestore{},
		),
		Entry(
			"using the plugin for backup and restore on Azure",
			&azureBackupPluginBackupPluginRestore{},
		),
		Entry(
			"using the plugin for backup and in-tree for restore on Azure",
			&azureBackupPluginBackupInTreeRestore{},
		),
		Entry(
			"using in-tree for backup and the plugin for restore on Azure",
			&azureBackupPluginInTreeBackupPluginRestore{},
		),
		Entry("using the plugin for backup and restore on GCS",
			&gcsBackupPluginBackupPluginRestore{},
		),
		Entry("using the plugin for backup and in-tree for restore on GCS",
			&gcsBackupPluginBackupInTreeRestore{},
		),
		Entry(
			"using in-tree for backup and the plugin for restore on GCS",
			&gcsBackupPluginInTreeBackupPluginRestore{},
		),
	)
})
