package appworkload_test

import (
	"context"
	"errors"

	"code.cloudfoundry.org/korifi/statefulset-runner/controllers"
	"code.cloudfoundry.org/korifi/statefulset-runner/controllers/appworkload"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gstruct"
	appsv1 "k8s.io/api/apps/v1"
	policyv1 "k8s.io/api/policy/v1"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/intstr"
)

var _ = Describe("PDB", func() {
	var (
		creator   *appworkload.PDBUpdater
		stSet     *appsv1.StatefulSet
		ctx       context.Context
		instances int32
	)

	BeforeEach(func() {
		creator = appworkload.NewPDBUpdater(fakeClient)
		instances = 2

		stSet = &appsv1.StatefulSet{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "name",
				Namespace: "namespace",
				UID:       "uid",
				Labels: map[string]string{
					controllers.LabelGUID:    "label-guid",
					appworkload.LabelVersion: "label-version",
				},
			},
			Spec: appsv1.StatefulSetSpec{
				Replicas: &instances,
				Selector: &metav1.LabelSelector{
					MatchLabels: map[string]string{
						controllers.LabelGUID:    "label-guid",
						appworkload.LabelVersion: "label-version",
					},
				},
			},
		}

		ctx = context.Background()
	})

	Describe("Update", func() {
		var updateErr error
		JustBeforeEach(func() {
			updateErr = creator.Update(ctx, stSet)
		})

		It("succeeds", func() {
			Expect(updateErr).NotTo(HaveOccurred())
		})

		It("creates a pod disruption budget", func() {
			Expect(fakeClient.CreateCallCount()).To(Equal(1))

			_, obj, createOpts := fakeClient.CreateArgsForCall(0)

			Expect(obj).To(BeAssignableToTypeOf(&policyv1.PodDisruptionBudget{}))
			pdb := obj.(*policyv1.PodDisruptionBudget)

			Expect(pdb.Namespace).To(Equal("namespace"))
			Expect(pdb.Name).To(Equal("name"))
			Expect(pdb.Spec.MinAvailable).To(PointTo(Equal(intstr.FromString("50%"))))
			Expect(pdb.Spec.Selector.MatchLabels).To(HaveKeyWithValue(controllers.LabelGUID, stSet.Labels[controllers.LabelGUID]))
			Expect(pdb.Spec.Selector.MatchLabels).To(HaveKeyWithValue(appworkload.LabelVersion, stSet.Labels[appworkload.LabelVersion]))
			Expect(pdb.OwnerReferences).To(HaveLen(1))
			Expect(pdb.OwnerReferences[0].Name).To(Equal(stSet.Name))
			Expect(pdb.OwnerReferences[0].UID).To(Equal(stSet.UID))

			Expect(createOpts).To(BeEmpty())
		})

		When("pod disruption budget creation fails", func() {
			BeforeEach(func() {
				fakeClient.CreateReturns(errors.New("boom"))
			})

			It("should propagate the error", func() {
				Expect(updateErr).To(MatchError(ContainSubstring("boom")))
			})
		})

		When("the statefulset has less than 2 target instances", func() {
			var instances int32

			BeforeEach(func() {
				instances = 1
				stSet.Spec.Replicas = &instances
			})

			It("does not create but does try to delete pdb", func() {
				Expect(fakeClient.CreateCallCount()).To(BeZero())
				Expect(fakeClient.DeleteAllOfCallCount()).To(Equal(1))
			})

			When("there is no PDB already", func() {
				BeforeEach(func() {
					fakeClient.DeleteReturns(k8serrors.NewNotFound(schema.GroupResource{}, "nope"))
				})

				It("succeeds", func() {
					Expect(updateErr).NotTo(HaveOccurred())
				})
			})

			When("deleting the PDB fails", func() {
				BeforeEach(func() {
					fakeClient.DeleteAllOfReturns(errors.New("oops"))
				})

				It("returns an error", func() {
					Expect(updateErr).To(MatchError(ContainSubstring("oops")))
				})
			})
		})

		When("the pod distruption budget already exists", func() {
			BeforeEach(func() {
				fakeClient.CreateReturns(k8serrors.NewAlreadyExists(schema.GroupResource{}, "boom"))
			})

			It("succeeds", func() {
				Expect(updateErr).NotTo(HaveOccurred())
			})
		})
	})
})
