/*
 * This file is part of the CDI project
 *
 * 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.
 *
 * Copyright 2019 Red Hat, Inc.
 *
 */

package webhooks

import (
	"encoding/json"

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

	admissionv1 "k8s.io/api/admission/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"

	cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
	cdiclient "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned/fake"
	sdkapi "kubevirt.io/controller-lifecycle-operator-sdk/api"
)

var (
	block   = cdiv1.CDIUninstallStrategyBlockUninstallIfWorkloadsExist
	noBlock = cdiv1.CDIUninstallStrategyRemoveWorkloads
)

var _ = Describe("CDI Delete Webhook", func() {
	Context("with CDI admission review", func() {
		DescribeTable("should accept with no DataVolumes present", func(strategy *cdiv1.CDIUninstallStrategy, op admissionv1.Operation) {
			cdi := &cdiv1.CDI{
				ObjectMeta: metav1.ObjectMeta{
					Name: "cdi",
				},
				Spec: cdiv1.CDISpec{
					UninstallStrategy: strategy,
				},
				Status: cdiv1.CDIStatus{
					Status: sdkapi.Status{
						Phase: sdkapi.PhaseDeployed,
					},
				},
			}

			bytes, _ := json.Marshal(cdi)

			ar := &admissionv1.AdmissionReview{
				Request: &admissionv1.AdmissionRequest{
					Operation: op,
					Resource: metav1.GroupVersionResource{
						Group:    cdiv1.SchemeGroupVersion.Group,
						Version:  cdiv1.SchemeGroupVersion.Version,
						Resource: "cdis",
					},
					OldObject: runtime.RawExtension{
						Raw: bytes,
					},
				},
			}

			resp := validateCDIs(ar)
			Expect(resp.Allowed).To(BeTrue())
		}, Entry("BLOCK DELETE", &block, admissionv1.Delete),
			Entry("BLOCK UPDATE", &block, admissionv1.Update),
			Entry("NO BLOCK DELETE", &noBlock, admissionv1.Delete),
			Entry("NO BLOCK UPDATE", &noBlock, admissionv1.Update),
			Entry("EMPTY DELETE", nil, admissionv1.Delete),
			Entry("EMPTY UPDATE", nil, admissionv1.Update),
		)

		DescribeTable("should accept with DataVolumes present", func(strategy *cdiv1.CDIUninstallStrategy, op admissionv1.Operation) {
			cdi := &cdiv1.CDI{
				ObjectMeta: metav1.ObjectMeta{
					Name: "cdi",
				},
				Spec: cdiv1.CDISpec{
					UninstallStrategy: strategy,
				},
				Status: cdiv1.CDIStatus{
					Status: sdkapi.Status{
						Phase: sdkapi.PhaseDeployed,
					},
				},
			}

			bytes, _ := json.Marshal(cdi)

			ar := &admissionv1.AdmissionReview{
				Request: &admissionv1.AdmissionRequest{
					Operation: op,
					Resource: metav1.GroupVersionResource{
						Group:    cdiv1.SchemeGroupVersion.Group,
						Version:  cdiv1.SchemeGroupVersion.Version,
						Resource: "cdis",
					},
					OldObject: runtime.RawExtension{
						Raw: bytes,
					},
				},
			}

			resp := validateCDIs(ar, newDataVolumeWithName("foo"))
			Expect(resp.Allowed).To(BeTrue())
		}, Entry("BLOCK UPDATE", &block, admissionv1.Update),
			Entry("NO BLOCK DELETE", &noBlock, admissionv1.Delete),
			Entry("NO BLOCK UPDATE", &noBlock, admissionv1.Update),
			Entry("EMPTY DELETE", nil, admissionv1.Delete),
			Entry("EMPTY UPDATE", nil, admissionv1.Update),
		)

		It("should reject with DataVolumes present", func() {
			cdi := &cdiv1.CDI{
				ObjectMeta: metav1.ObjectMeta{
					Name: "cdi",
				},
				Spec: cdiv1.CDISpec{
					UninstallStrategy: &block,
				},
				Status: cdiv1.CDIStatus{
					Status: sdkapi.Status{
						Phase: sdkapi.PhaseDeployed,
					},
				},
			}

			bytes, _ := json.Marshal(cdi)

			ar := &admissionv1.AdmissionReview{
				Request: &admissionv1.AdmissionRequest{
					Operation: admissionv1.Delete,
					Resource: metav1.GroupVersionResource{
						Group:    cdiv1.SchemeGroupVersion.Group,
						Version:  cdiv1.SchemeGroupVersion.Version,
						Resource: "cdis",
					},
					OldObject: runtime.RawExtension{
						Raw: bytes,
					},
				},
			}

			resp := validateCDIs(ar, newDataVolumeWithName("foo"))
			Expect(resp.Allowed).To(BeFalse())
			Expect(resp.Result.Message).To(ContainSubstring("rejecting the uninstall request, since there are still 1 DataVolumes present."))
		})

		It("should reject with DataVolumes present and oldobject not populated", func() {
			cdi := &cdiv1.CDI{
				ObjectMeta: metav1.ObjectMeta{
					Name: "cdi",
				},
				Spec: cdiv1.CDISpec{
					UninstallStrategy: &block,
				},
				Status: cdiv1.CDIStatus{
					Status: sdkapi.Status{
						Phase: sdkapi.PhaseDeployed,
					},
				},
			}

			ar := &admissionv1.AdmissionReview{
				Request: &admissionv1.AdmissionRequest{
					Operation: admissionv1.Delete,
					Name:      cdi.Name,
					Resource: metav1.GroupVersionResource{
						Group:    cdiv1.SchemeGroupVersion.Group,
						Version:  cdiv1.SchemeGroupVersion.Version,
						Resource: "cdis",
					},
				},
			}

			resp := validateCDIs(ar, cdi, newDataVolumeWithName("foo"))
			Expect(resp.Allowed).To(BeFalse())
			Expect(resp.Result.Message).To(ContainSubstring("rejecting the uninstall request, since there are still 1 DataVolumes present."))
		})

		It("should allow error CDI to be deleted with DataVolumes present", func() {
			cdi := &cdiv1.CDI{
				ObjectMeta: metav1.ObjectMeta{
					Name: "cdi",
				},
				Spec: cdiv1.CDISpec{
					UninstallStrategy: &block,
				},
				Status: cdiv1.CDIStatus{
					Status: sdkapi.Status{
						Phase: sdkapi.PhaseError,
					},
				},
			}

			bytes, _ := json.Marshal(cdi)

			ar := &admissionv1.AdmissionReview{
				Request: &admissionv1.AdmissionRequest{
					Operation: admissionv1.Delete,
					Resource: metav1.GroupVersionResource{
						Group:    cdiv1.SchemeGroupVersion.Group,
						Version:  cdiv1.SchemeGroupVersion.Version,
						Resource: "cdis",
					},
					OldObject: runtime.RawExtension{
						Raw: bytes,
					},
				},
			}

			resp := validateCDIs(ar, newDataVolumeWithName("foo"))
			Expect(resp.Allowed).To(BeTrue())
		})

		It("should reject weird resource", func() {
			bytes, _ := json.Marshal(newDataVolumeWithName("foo"))

			ar := &admissionv1.AdmissionReview{
				Request: &admissionv1.AdmissionRequest{
					Operation: admissionv1.Delete,
					Resource: metav1.GroupVersionResource{
						Group:    cdiv1.SchemeGroupVersion.Group,
						Version:  cdiv1.SchemeGroupVersion.Version,
						Resource: "datavolumes",
					},
					OldObject: runtime.RawExtension{
						Raw: bytes,
					},
				},
			}

			resp := validateCDIs(ar)
			Expect(resp.Allowed).To(BeFalse())
		})

	})
})

func newDataVolumeWithName(name string) *cdiv1.DataVolume {
	return &cdiv1.DataVolume{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
}

func validateCDIs(ar *admissionv1.AdmissionReview, cdiObjects ...runtime.Object) *admissionv1.AdmissionResponse {
	client := cdiclient.NewSimpleClientset(cdiObjects...)
	wh := NewCDIValidatingWebhook(client)
	return serve(ar, wh)
}
