/*
 * This file is part of the KubeVirt 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 The KubeVirt Authors.
 *
 */
package util

import (
	"time"

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

	k8sv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	v1 "kubevirt.io/api/core/v1"
)

var _ = Describe("Operator Client", func() {

	Describe("Conditions and Finalizers", func() {
		getKubeVirtWithCreatedConditionAndRandomFinalizer := func() *v1.KubeVirt {
			return &v1.KubeVirt{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test",
					Namespace: "ns",
					Finalizers: []string{
						"oldFinalizer",
					},
				},
				Status: v1.KubeVirtStatus{
					Phase: v1.KubeVirtPhaseDeploying,
					Conditions: []v1.KubeVirtCondition{
						{
							Type:    v1.KubeVirtConditionCreated,
							Status:  k8sv1.ConditionFalse,
							Reason:  "OldReason",
							Message: "old message",
						},
					},
				},
			}
		}

		var kv *v1.KubeVirt

		BeforeEach(func() {
			kv = getKubeVirtWithCreatedConditionAndRandomFinalizer()
		})

		Describe("Updating a condition", func() {

			Context("When it doesn't exist yet", func() {
				It("Should add the condition", func() {
					updateCondition(kv, v1.KubeVirtConditionAvailable, k8sv1.ConditionTrue, "NewReason", "new message")
					Expect(kv.Status.Conditions).To(HaveLen(2), "should have 2 conditions")
					condition1 := kv.Status.Conditions[0]
					Expect(condition1.Type).To(Equal(v1.KubeVirtConditionCreated), "should keep old condition type")
					condition2 := kv.Status.Conditions[1]
					Expect(condition2.Type).To(Equal(v1.KubeVirtConditionAvailable), "should set correct condition type")
					Expect(condition2.Status).To(Equal(k8sv1.ConditionTrue), "should set correct condition status")
					Expect(condition2.Reason).To(Equal("NewReason"), "should set correct condition reason")
					Expect(condition2.Message).To(Equal("new message"), "should set correct condition message")
				})
			})

			Context("When it exists", func() {
				It("Should update the condition", func() {
					updateCondition(kv, v1.KubeVirtConditionCreated, k8sv1.ConditionTrue, "NewReason", "new message")
					Expect(kv.Status.Conditions).To(HaveLen(1), "should still have 1 condition")
					condition1 := kv.Status.Conditions[0]
					Expect(condition1.Type).To(Equal(v1.KubeVirtConditionCreated), "should keep old condition type")
					Expect(condition1.Status).To(Equal(k8sv1.ConditionTrue), "should update condition status")
					Expect(condition1.Reason).To(Equal("NewReason"), "should update condition reason")
					Expect(condition1.Message).To(Equal("new message"), "should update condition message")
				})
			})

		})

		Describe("Removing a condition", func() {

			Context("When it doesn't exist", func() {
				It("Should not change existing conditions", func() {
					removeCondition(kv, v1.KubeVirtConditionAvailable)
					Expect(kv.Status.Conditions).To(HaveLen(1), "should still have 1 condition")
					condition1 := kv.Status.Conditions[0]
					Expect(condition1.Type).To(Equal(v1.KubeVirtConditionCreated))
					Expect(condition1.Status).To(Equal(k8sv1.ConditionFalse))
					Expect(condition1.Reason).To(Equal("OldReason"))
					Expect(condition1.Message).To(Equal("old message"))
				})
			})

			Context("When it exists", func() {
				It("Should remove the condition", func() {
					removeCondition(kv, v1.KubeVirtConditionCreated)
					Expect(kv.Status.Conditions).To(BeEmpty(), "should have no condition")
				})
			})

		})

		Describe("Adding a finalizer", func() {
			Context("When another one already exists", func() {
				It("Should add it", func() {
					SetFinalizer(kv)
					Expect(kv.Finalizers).To(HaveLen(2), "should have 2 finalizers")
					Expect(kv.Finalizers[0]).To(Equal("oldFinalizer"), "should keep first old finalizer")
					Expect(kv.Finalizers[1]).To(Equal(KubeVirtFinalizer), "should add new finalizer")
				})
				It("Should not add it again", func() {
					SetFinalizer(kv)
					SetFinalizer(kv)
					Expect(kv.Finalizers).To(HaveLen(2), "should still have 2 finalizers")
					Expect(kv.Finalizers[0]).To(Equal("oldFinalizer"), "should keep first old finalizer")
					Expect(kv.Finalizers[1]).To(Equal(KubeVirtFinalizer), "should keep second old finalizer")
				})
				It("Should replace the deprecated finalizer on upgrade", func() {
					kv.Finalizers = []string{deprecatedKubeVirtFinalizer}
					SetFinalizer(kv)
					Expect(kv.Finalizers).To(ConsistOf(KubeVirtFinalizer))
				})
			})
		})

		Describe("UnsetFinalizer", func() {
			It("Should remove the current finalizer", func() {
				kv.Finalizers = []string{KubeVirtFinalizer}
				UnsetFinalizer(kv)
				Expect(kv.Finalizers).To(BeEmpty())
			})
			It("Should remove the deprecated finalizer", func() {
				kv.Finalizers = []string{deprecatedKubeVirtFinalizer}
				UnsetFinalizer(kv)
				Expect(kv.Finalizers).To(BeEmpty())
			})
			It("Should preserve finalizers from other controllers should they exist", func() {
				kv.Finalizers = []string{"otherFinalizer", KubeVirtFinalizer}
				UnsetFinalizer(kv)
				Expect(kv.Finalizers).To(ConsistOf("otherFinalizer"))
			})
		})

		Describe("Setting condition timestamps", func() {
			var kv1 *v1.KubeVirt
			var kv2 *v1.KubeVirt
			empty := metav1.Time{}
			now := metav1.Time{
				Time: time.Now(),
			}

			BeforeEach(func() {
				kv1 = &v1.KubeVirt{
					Status: v1.KubeVirtStatus{
						Conditions: []v1.KubeVirtCondition{},
					},
				}
				kv2 = &v1.KubeVirt{
					Status: v1.KubeVirtStatus{
						Conditions: []v1.KubeVirtCondition{},
					},
				}
			})
			Context("For new condition", func() {
				It("should set lastProbeTime only", func() {
					kv2.Status.Conditions = append(kv1.Status.Conditions, v1.KubeVirtCondition{
						Type: v1.KubeVirtConditionAvailable,
					})
					SetConditionTimestamps(kv1, kv2)
					Expect(kv2.Status.Conditions[0].LastProbeTime).ToNot(Equal(empty))
					Expect(kv2.Status.Conditions[0].LastProbeTime.Time).To(BeTemporally(">", now.Time))
					Expect(kv2.Status.Conditions[0].LastTransitionTime).To(Equal(empty))
				})
			})
			Context("For unmodified condition", func() {
				It("should keep timestamps", func() {
					kv1.Status.Conditions = append(kv1.Status.Conditions, v1.KubeVirtCondition{
						Type:   v1.KubeVirtConditionAvailable,
						Status: k8sv1.ConditionTrue,
					})
					kv2.Status.Conditions = append(kv2.Status.Conditions, v1.KubeVirtCondition{
						Type:          v1.KubeVirtConditionAvailable,
						Status:        k8sv1.ConditionTrue,
						LastProbeTime: now,
					})
					SetConditionTimestamps(kv1, kv2)
					Expect(kv2.Status.Conditions[0].LastProbeTime).To(Equal(now))
					Expect(kv2.Status.Conditions[0].LastTransitionTime).To(Equal(empty))
				})
			})
			Context("For modified condition", func() {
				It("should update both timestamps", func() {
					now := metav1.Time{
						Time: time.Now(),
					}
					kv1.Status.Conditions = append(kv1.Status.Conditions, v1.KubeVirtCondition{
						Type:   v1.KubeVirtConditionAvailable,
						Status: k8sv1.ConditionTrue,
					})
					kv2.Status.Conditions = append(kv2.Status.Conditions, v1.KubeVirtCondition{
						Type:          v1.KubeVirtConditionAvailable,
						Status:        k8sv1.ConditionFalse,
						LastProbeTime: now,
					})
					SetConditionTimestamps(kv1, kv2)
					Expect(kv2.Status.Conditions[0].LastProbeTime.Time).To(BeTemporally(">=", now.Time))
					Expect(kv2.Status.Conditions[0].LastTransitionTime).ToNot(Equal(empty))
					Expect(kv2.Status.Conditions[0].LastTransitionTime).To(Equal(kv2.Status.Conditions[0].LastProbeTime))
				})
			})
		})

	})
})
