package tests_test

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

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

	route1client "github.com/openshift/client-go/route/clientset/versioned"

	v1 "k8s.io/api/core/v1"
	networkingv1 "k8s.io/api/networking/v1"
	storagev1 "k8s.io/api/storage/v1"
	apiequality "k8s.io/apimachinery/pkg/api/equality"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/clientcmd"

	cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
	"kubevirt.io/containerized-data-importer/pkg/common"
	controller "kubevirt.io/containerized-data-importer/pkg/controller/common"
	"kubevirt.io/containerized-data-importer/tests/framework"
	"kubevirt.io/containerized-data-importer/tests/utils"
)

const (
	ingressURL = "www.super-duper-test.ingress.tt.org"
	routeName  = "cdi-uploadproxy"
)

var (
	defaultURL = ""
)

var _ = Describe("CDI storage class config tests", Serial, func() {
	var (
		f                   = framework.NewFramework("cdiconfig-test")
		defaultSc, secondSc *storagev1.StorageClass
		origSpec            *cdiv1.CDIConfigSpec
	)

	BeforeEach(func() {
		storageClasses, err := f.K8sClient.StorageV1().StorageClasses().List(context.TODO(), metav1.ListOptions{})
		Expect(err).ToNot(HaveOccurred())

		for _, sc := range storageClasses.Items {
			if defaultClassValue, ok := sc.Annotations[controller.AnnDefaultStorageClass]; ok {
				if defaultClassValue == "true" {
					defaultSc = &sc
					break
				}
			}
		}

		config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		origSpec = config.Spec.DeepCopy()

		By("Unsetting storage class override if set.")
		err = utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
			config.ScratchSpaceStorageClass = nil
		})
		Expect(err).ToNot(HaveOccurred())

		By("Waiting for the default scratch space storage class to be blank")
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			return config.Status.ScratchSpaceStorageClass
		}, time.Second*30, time.Second).Should(BeEmpty())
	})

	AfterEach(func() {
		if secondSc != nil {
			By("Unmarking default " + secondSc.Name)
			err := SetStorageClassDefault(f, secondSc.Name, false)
			Expect(err).ToNot(HaveOccurred())
			secondSc = nil
		}
		if defaultSc != nil {
			By("Restoring default to " + defaultSc.Name)
			err := SetStorageClassDefault(f, defaultSc.Name, true)
			Expect(err).ToNot(HaveOccurred())
			defaultSc = nil
		}

		By("Restoring CDIConfig to original state")
		err := utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
			origSpec.DeepCopyInto(config)
		})
		Expect(err).ToNot(HaveOccurred())
		Eventually(func() bool {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			return apiequality.Semantic.DeepEqual(config.Spec, *origSpec)
		}, timeout, pollingInterval).Should(BeTrue(), "CDIConfig not properly restored to original value")

		Eventually(func() bool {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			return !apiequality.Semantic.DeepEqual(config.Status, cdiv1.CDIConfigStatus{})
		}, timeout, pollingInterval).Should(BeTrue(), "CDIConfig status not restored by config controller")
	})

	It("[test_id:3962]should have scratchSpaceStorageClass set to blank", func() {
		if defaultSc == nil {
			Skip("No default storage class found, skipping test")
		}
		By("Expecting default storage class to be: " + defaultSc.Name)
		config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		Expect(config.Status.ScratchSpaceStorageClass).To(BeEmpty())
	})

	It("[test_id:3965]should have scratchSpaceStorageClass set to blank if you specify an invalid override", func() {
		if defaultSc == nil {
			Skip("No default storage class found, skipping test")
		}
		By("Expecting default storage class to be " + defaultSc.Name)
		invalid := "invalidsc"
		err := utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
			config.ScratchSpaceStorageClass = &invalid
		})
		Expect(err).ToNot(HaveOccurred())
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			if config.Spec.ScratchSpaceStorageClass == nil {
				return ""
			}
			return *config.Spec.ScratchSpaceStorageClass
		}, time.Second*30, time.Second).Should(Equal(invalid))
		config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		Expect(config.Status.ScratchSpaceStorageClass).To(BeEmpty())
	})

	It("[test_id:3967]Should use the override even if a different default is set", func() {
		storageClasses, err := f.K8sClient.StorageV1().StorageClasses().List(context.TODO(), metav1.ListOptions{})
		Expect(err).ToNot(HaveOccurred())
		if len(storageClasses.Items) < 2 {
			Skip("Not enough storage classes to test overrider")
		}
		config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		Expect(config.Status.ScratchSpaceStorageClass).To(BeEmpty())
		var override string
		for _, sc := range storageClasses.Items {
			if sc.Name != defaultSc.Name {
				// Found other class, now set it to default.
				override = sc.Name
				break
			}
		}
		err = utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
			config.ScratchSpaceStorageClass = &override
		})
		Expect(err).ToNot(HaveOccurred())
		By("Verifying the override " + override + " is now the scratchspace name")
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			return config.Status.ScratchSpaceStorageClass
		}, time.Second*30, time.Second).Should(Equal(override))
	})
})

var _ = Describe("CDI ingress config tests, using manifests", Serial, func() {
	var (
		f                       = framework.NewFramework("cdiconfig-test")
		routeStart              = func() string { return fmt.Sprintf("%s-%s.", routeName, f.CdiInstallNs) }
		manifestFile            string
		origUploadProxyOverride *string
	)

	BeforeEach(func() {
		cfg, err := clientcmd.BuildConfigFromFlags(f.KubeURL, f.KubeConfig)
		Expect(err).ToNot(HaveOccurred())
		By("Checking if a route exists, we set that as default")
		openshiftClient, err := route1client.NewForConfig(cfg)
		Expect(err).ToNot(HaveOccurred())
		_, err = openshiftClient.RouteV1().Routes(f.CdiInstallNs).Get(context.TODO(), "cdi-uploadproxy", metav1.GetOptions{})
		if err == nil {
			By("setting defaultURL to route")
			Eventually(func() bool {
				config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
				Expect(err).ToNot(HaveOccurred())
				if config.Status.UploadProxyURL == nil {
					return false
				}
				return strings.HasPrefix(*config.Status.UploadProxyURL, routeStart())
			}, time.Second*30, time.Second).Should(BeTrue())
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			defaultURL = *config.Status.UploadProxyURL
		}
		By("Making sure no url is set")
		config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		if config.Spec.UploadProxyURLOverride != nil {
			err = utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
				origUploadProxyOverride = config.UploadProxyURLOverride
				config.UploadProxyURLOverride = nil
			})
			Expect(err).ToNot(HaveOccurred())
		}
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			if config.Status.UploadProxyURL == nil {
				return ""
			}
			return *config.Status.UploadProxyURL
		}, time.Second*30, time.Second).Should(Equal(defaultURL))
	})

	AfterEach(func() {
		_, err := f.RunKubectlCommand("delete", "-f", manifestFile, "-n", f.CdiInstallNs)
		Expect(err).ShouldNot(HaveOccurred())

		matchingVals := []string{defaultURL}
		if origUploadProxyOverride != nil {
			matchingVals = append(matchingVals, *origUploadProxyOverride)
		}
		By("Restoring original UploadProxyURLOverride")
		Expect(utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
			config.UploadProxyURLOverride = origUploadProxyOverride
		})).To(Succeed())

		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			if config.Status.UploadProxyURL == nil {
				return ""
			}
			return *config.Status.UploadProxyURL
		}, time.Second*30, time.Second).Should(BeElementOf(matchingVals))
	})

	It("[test_id:4949]Should properly react to network ingress", func() {
		manifestFile = "manifests/ingressNetworkApigroup.yaml"
		out, err := f.RunKubectlCommand("create", "-f", manifestFile, "-n", f.CdiInstallNs)
		fmt.Fprintf(GinkgoWriter, "INFO: Output from kubectl: %s\n", out)
		Expect(err).ToNot(HaveOccurred())
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			if config.Status.UploadProxyURL != nil {
				return *config.Status.UploadProxyURL
			}
			return ""
		}, time.Second*30, time.Second).Should(Equal("test.manifest.example.com"))
	})

	It("[test_id:4950]Keep current value on no http rule ingress", func() {
		manifestFile = "manifests/ingressNoHttp.yaml"
		controllerPod, err := utils.FindPodByPrefix(f.K8sClient, f.CdiInstallNs, cdiDeploymentPodPrefix, common.CDILabelSelector)
		Expect(err).ToNot(HaveOccurred())
		currentRestarts := controllerPod.Status.ContainerStatuses[0].RestartCount
		fmt.Fprintf(GinkgoWriter, "INFO: Current number of restarts: %d\n", currentRestarts)
		out, err := f.RunKubectlCommand("create", "-f", manifestFile, "-n", f.CdiInstallNs)
		fmt.Fprintf(GinkgoWriter, "INFO: Output from kubectl: %s\n", out)
		Expect(err).ToNot(HaveOccurred())
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			if config.Status.UploadProxyURL != nil {
				return *config.Status.UploadProxyURL
			}
			return ""
		}, time.Second*30, time.Second).Should(Equal(defaultURL))
		for i := 0; i < 10; i++ {
			// Check for 20 seconds if the deployment pod crashed.
			time.Sleep(2 * time.Second)
			controllerPod, err = utils.FindPodByPrefix(f.K8sClient, f.CdiInstallNs, cdiDeploymentPodPrefix, common.CDILabelSelector)
			Expect(err).ToNot(HaveOccurred())
			// Restarts will increase if we crashed due to null pointer.
			Expect(currentRestarts).To(Equal(controllerPod.Status.ContainerStatuses[0].RestartCount))
		}
	})
})

var _ = Describe("CDI ingress config tests", Serial, func() {
	var (
		f                       = framework.NewFramework("cdiconfig-test")
		routeStart              = func() string { return fmt.Sprintf("%s-%s.", routeName, f.CdiInstallNs) }
		ingress                 *networkingv1.Ingress
		origUploadProxyOverride *string
	)

	BeforeEach(func() {
		cfg, err := clientcmd.BuildConfigFromFlags(f.KubeURL, f.KubeConfig)
		Expect(err).ToNot(HaveOccurred())
		By("Checking if a route exists, we set that as default")
		openshiftClient, err := route1client.NewForConfig(cfg)
		Expect(err).ToNot(HaveOccurred())
		_, err = openshiftClient.RouteV1().Routes(f.CdiInstallNs).Get(context.TODO(), "cdi-uploadproxy", metav1.GetOptions{})
		if err == nil {
			By("setting defaultURL to route")
			Eventually(func() bool {
				config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
				Expect(err).ToNot(HaveOccurred())
				if config.Status.UploadProxyURL == nil {
					return false
				}
				return strings.HasPrefix(*config.Status.UploadProxyURL, routeStart())
			}, time.Second*30, time.Second).Should(BeTrue())
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			defaultURL = *config.Status.UploadProxyURL
		}
		By("Making sure no url is set")
		config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		if config.Spec.UploadProxyURLOverride != nil {
			err = utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
				origUploadProxyOverride = config.UploadProxyURLOverride
				config.UploadProxyURLOverride = nil
			})
			Expect(err).ToNot(HaveOccurred())
		}
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			if config.Status.UploadProxyURL == nil {
				return ""
			}
			return *config.Status.UploadProxyURL
		}, time.Second*30, time.Second).Should(Equal(defaultURL))
	})

	AfterEach(func() {
		matchingVals := []string{defaultURL}
		if origUploadProxyOverride != nil {
			matchingVals = append(matchingVals, *origUploadProxyOverride)
		}
		By("Restoring original UploadProxyURLOverride")
		err := utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
			config.UploadProxyURLOverride = origUploadProxyOverride
		})
		Expect(err).ToNot(HaveOccurred())

		if ingress != nil {
			By("Cleaning up ingress")
			err := f.K8sClient.NetworkingV1().Ingresses(ingress.Namespace).Delete(context.TODO(), ingress.Name, metav1.DeleteOptions{})
			Expect(err).ToNot(HaveOccurred())
			Eventually(func() string {
				config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
				Expect(err).ToNot(HaveOccurred())
				if config.Status.UploadProxyURL == nil {
					return ""
				}
				return *config.Status.UploadProxyURL
			}, time.Second*30, time.Second).Should(BeElementOf(matchingVals))
		}
	})

	It("[test_id:3960]Should set uploadProxyURL if override is not defined", func() {
		// TODO, don't hard code "cdi-uploadproxy", read it from container env of cdi-deployment deployment.
		ingress = createIngress("test-ingress", f.CdiInstallNs, "cdi-uploadproxy", ingressURL)
		_, err := f.K8sClient.NetworkingV1().Ingresses(f.CdiInstallNs).Create(context.TODO(), ingress, metav1.CreateOptions{})
		Expect(err).ToNot(HaveOccurred())
		By("Expecting uploadproxy url to be " + ingressURL)
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			if config.Status.UploadProxyURL == nil {
				return ""
			}
			return *config.Status.UploadProxyURL
		}, time.Second*30, time.Second).Should(Equal(ingressURL))
	})

	It("[test_id:3961]Should keep override uploadProxyURL if override is defined", func() {
		override := "www.override.tt.org"
		err := utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
			config.UploadProxyURLOverride = &override
		})
		Expect(err).ToNot(HaveOccurred())
		// TODO, don't hard code "cdi-uploadproxy", read it from container env of cdi-deployment deployment.
		ingress = createIngress("test-ingress", f.CdiInstallNs, "cdi-uploadproxy", ingressURL)
		_, err = f.K8sClient.NetworkingV1().Ingresses(f.CdiInstallNs).Create(context.TODO(), ingress, metav1.CreateOptions{})
		Expect(err).ToNot(HaveOccurred())
		By("Expecting uploadproxy url to be " + override)
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			if config.Status.UploadProxyURL == nil {
				return ""
			}
			return *config.Status.UploadProxyURL
		}, time.Second*30, time.Second).Should(Equal(override))
	})

	It("Should not crash and uploadProxy empty, if ingress defined without service", func() {
		ingress = createNoServiceIngress("test-ingress", f.CdiInstallNs)
		// The cdi controller will not process this ingress because it doesn't have the url we are looking for.
		_, err := f.K8sClient.NetworkingV1().Ingresses(f.CdiInstallNs).Create(context.TODO(), ingress, metav1.CreateOptions{})
		Expect(err).ToNot(HaveOccurred())
		By("Expecting uploadproxy url to be the default url")
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			proxyURL := ""
			if config.Status.UploadProxyURL != nil {
				proxyURL = *config.Status.UploadProxyURL
			}
			return proxyURL
		}, time.Second*30, time.Second).Should(Equal(defaultURL))
	})
})

var _ = Describe("CDI route config tests", Serial, func() {
	var (
		f                       = framework.NewFramework("cdiconfig-test")
		routeStart              = func() string { return fmt.Sprintf("%s-%s.", routeName, f.CdiInstallNs) }
		openshiftClient         *route1client.Clientset
		origUploadProxyOverride *string
	)

	BeforeEach(func() {
		cfg, err := clientcmd.BuildConfigFromFlags(f.KubeURL, f.KubeConfig)
		Expect(err).ToNot(HaveOccurred())
		openshiftClient, err = route1client.NewForConfig(cfg)
		Expect(err).ToNot(HaveOccurred())
		config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		if config.Spec.UploadProxyURLOverride != nil {
			err = utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
				origUploadProxyOverride = config.UploadProxyURLOverride
				config.UploadProxyURLOverride = nil
			})
			Expect(err).ToNot(HaveOccurred())
		}
		_, err = openshiftClient.RouteV1().Routes(f.CdiInstallNs).Get(context.TODO(), "cdi-uploadproxy", metav1.GetOptions{})
		if err != nil {
			Skip("Unable to list routes, skipping")
		}
		By("Making sure no url is set to default route")
		Eventually(func() bool {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			if config.Status.UploadProxyURL == nil {
				return false
			}
			return strings.HasPrefix(*config.Status.UploadProxyURL, routeStart())
		}, time.Second*30, time.Second).Should(BeTrue())
	})

	AfterEach(func() {
		By("Restoring original UploadProxyURLOverride")
		err := utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
			config.UploadProxyURLOverride = origUploadProxyOverride
		})
		Expect(err).ToNot(HaveOccurred())
	})

	It("[test_id:4951]Should override uploadProxyURL if override is set", func() {
		if openshiftClient == nil {
			Skip("Routes not available")
		}
		override := "www.override.tt.org"
		err := utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
			config.UploadProxyURLOverride = &override
		})
		Expect(err).ToNot(HaveOccurred())
		Eventually(func() string {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			if config.Status.UploadProxyURL == nil {
				return ""
			}
			return *config.Status.UploadProxyURL
		}, time.Second*30, time.Second).Should(Equal(override))
	})
})

var _ = Describe("CDIConfig instance management", Serial, func() {
	f := framework.NewFramework("cdiconfig-test")

	Context("[Destructive]", Serial, func() {
		It("[test_id:4952]Should re-create the object if deleted", func() {
			By("Verifying the object exists")
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			// Save the UID, so we can check it against a new one.
			orgUID := config.GetUID()
			_, _ = fmt.Fprintf(GinkgoWriter, "Original CDIConfig UID: %s\n", orgUID)
			By("Deleting the object")
			err = f.CdiClient.CdiV1beta1().CDIConfigs().Delete(context.TODO(), config.Name, metav1.DeleteOptions{})
			Expect(err).ToNot(HaveOccurred())

			Eventually(func() bool {
				newConfig, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
				if err != nil {
					return false
				}
				_, _ = fmt.Fprintf(GinkgoWriter, "New CDIConfig UID: %s\n", newConfig.GetUID())
				return orgUID != newConfig.GetUID() && !apiequality.Semantic.DeepEqual(newConfig.Status, cdiv1.CDIConfigStatus{})
			}, time.Second*30, time.Second).Should(BeTrue())
		})
	})
})

var _ = Describe("Modifying CDIConfig spec tests", Serial, func() {
	var origSpec *cdiv1.CDIConfigSpec
	f := framework.NewFramework("cdiconfig-test")
	BeforeEach(func() {
		config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		origSpec = config.Spec.DeepCopy()
	})
	AfterEach(func() {
		By("Restoring CDIConfig to original state")
		err := utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
			origSpec.DeepCopyInto(config)
		})
		Expect(err).ToNot(HaveOccurred())
		Eventually(func() bool {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			return apiequality.Semantic.DeepEqual(config.Spec, *origSpec)
		}, timeout, pollingInterval).Should(BeTrue(), "CDIConfig not properly restored to original value")

		Eventually(func() bool {
			config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			return !apiequality.Semantic.DeepEqual(config.Status, cdiv1.CDIConfigStatus{})
		}, timeout, pollingInterval).Should(BeTrue(), "CDIConfig status not restored by config controller")
	})

	DescribeTable("Should disallow invalid global filesystem overhead values", func(overhead string, success bool) {
		err := utils.UpdateCDIConfigWithOptions(f.CrClient, metav1.UpdateOptions{DryRun: []string{"All"}}, func(config *cdiv1.CDIConfigSpec) {
			config.FilesystemOverhead = &cdiv1.FilesystemOverhead{
				Global: cdiv1.Percent(overhead),
			}
		})
		if success {
			Expect(err).ToNot(HaveOccurred())
		} else {
			Expect(err).To(HaveOccurred())
		}
	},
		Entry("[test_id:4714] Not a number1", "abc", false),
		Entry("[test_id:4674] Not a number2", "1.abc", false),
		Entry("[test_id:5011] Too big1", "1.0001", false),
		Entry("[test_id:5012] Too big2", "inf", false),
		Entry("[test_id:5013] Negative", "-0.1", false),
		Entry("one", "1", true),
		Entry("zero", "0", true),
		Entry("zero2", "0.0", true),
	)

	DescribeTable("Per-storageClass filesystem overhead value", func(overhead string, success bool) {
		defaultSCName := utils.DefaultStorageClass.GetName()
		err := utils.UpdateCDIConfigWithOptions(f.CrClient, metav1.UpdateOptions{}, func(config *cdiv1.CDIConfigSpec) {
			config.FilesystemOverhead = &cdiv1.FilesystemOverhead{
				Global:       "0.99", // Used to easily test that the update happened
				StorageClass: map[string]cdiv1.Percent{defaultSCName: cdiv1.Percent(overhead)},
			}
		})
		if success {
			Expect(err).ToNot(HaveOccurred())
			By("Waiting for the CDIConfig status to be updated by the controller")
			Eventually(func() bool {
				config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
				Expect(err).ToNot(HaveOccurred())
				return config.Status.FilesystemOverhead.Global == cdiv1.Percent("0.99")
			}, timeout, pollingInterval).Should(BeTrue(), "CDIConfig not set")
		} else {
			Expect(err).To(HaveOccurred())
		}

		config, _ := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
		if success {
			By(fmt.Sprintf("CDI Config spec %v", config.Spec.FilesystemOverhead.StorageClass))
			By(fmt.Sprintf("CDI Config status %v", config.Status.FilesystemOverhead.StorageClass))
			Expect(config.Status.FilesystemOverhead.StorageClass[defaultSCName]).To(Equal(cdiv1.Percent(overhead)))
		} else {
			Expect(config.Status.FilesystemOverhead.StorageClass[defaultSCName]).ToNot(Equal(cdiv1.Percent(overhead)))
		}
	},
		Entry("[test_id:5014]should not update if not a number1", "abc", false),
		Entry("[test_id:5015]should not update if not a number2", "1.abc", false),
		Entry("[test_id:5016]should not update if too big1", "1.01", false),
		Entry("[test_id:5017]should not update if too big2", "inf", false),
		Entry("[test_id:5918]should not update if value is negative", "-0.1", false),
		Entry("should update if value is one", "1", true),
		Entry("should update if value is zero", "0", true),
		Entry("should update if value is zero2", "0.0", true),
	)

})

func createIngress(name, ns, service, hostURL string) *networkingv1.Ingress {
	return &networkingv1.Ingress{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "networking/v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: ns,
		},
		Spec: networkingv1.IngressSpec{
			DefaultBackend: &networkingv1.IngressBackend{
				Service: &networkingv1.IngressServiceBackend{Name: service, Port: networkingv1.ServiceBackendPort{Number: 443}},
			},
			Rules: []networkingv1.IngressRule{
				{Host: hostURL},
			},
		},
	}
}

func createNoServiceIngress(name, ns string) *networkingv1.Ingress {
	apiGroup := "test.test"
	return &networkingv1.Ingress{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "networking/v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: ns,
			Labels: map[string]string{
				"nginx.org/ssl-services":                      "cdi-uploadproxy",
				"ingress.kubernetes.io/ssl-passthrough":       "true",
				"nginx.ingress.kubernetes.io/secure-backends": "true",
				"nginx.ingress.kubernetes.io/proxy-body-size": "0",
			},
		},
		Spec: networkingv1.IngressSpec{
			DefaultBackend: &networkingv1.IngressBackend{
				Resource: &v1.TypedLocalObjectReference{
					APIGroup: &apiGroup,
					Kind:     "test",
					Name:     "test",
				},
			},
			TLS: []networkingv1.IngressTLS{
				{
					Hosts: []string{
						"cdi-uploadproxy.example.com",
					},
				},
			},
		},
	}
}

func SetStorageClassDefault(f *framework.Framework, scName string, isDefault bool) error {
	sc, err := f.K8sClient.StorageV1().StorageClasses().Get(context.TODO(), scName, metav1.GetOptions{})
	if err != nil {
		return err
	}
	ann := sc.GetAnnotations()
	if ann == nil {
		ann = make(map[string]string)
	}
	ann[controller.AnnDefaultStorageClass] = strconv.FormatBool(isDefault)
	sc.SetAnnotations(ann)
	_, err = f.K8sClient.StorageV1().StorageClasses().Update(context.TODO(), sc, metav1.UpdateOptions{})
	Expect(err).ToNot(HaveOccurred())
	Eventually(func() string {
		sc, err := f.K8sClient.StorageV1().StorageClasses().Get(context.TODO(), scName, metav1.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		return sc.GetAnnotations()[controller.AnnDefaultStorageClass]
	}, time.Second*30, time.Second).Should(Equal(strconv.FormatBool(isDefault)))
	return nil
}
