package apps

//+kubebuilder:webhook:path=/mutate-korifi-cloudfoundry-org-v1alpha1-cfapp-apprev,mutating=true,failurePolicy=fail,sideEffects=None,groups=korifi.cloudfoundry.org,resources=cfapps,verbs=update,versions=v1alpha1,name=mcfapprev.korifi.cloudfoundry.org,admissionReviewVersions={v1,v1beta1}

import (
	"context"
	"encoding/json"
	"net/http"
	"strconv"

	korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1"
	ctrl "sigs.k8s.io/controller-runtime"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// AppRevWebhook does not implement the admission.Defaulter interface as we
// need to access both oldObject and (new)Object to determine the actual state
// change. So we use the lower-level admission.Handler interface
type AppRevWebhook struct {
	decoder admission.Decoder
}

var apprevlog = logf.Log.WithName("apprev-webhook")

func (r *AppRevWebhook) SetupWebhookWithManager(mgr ctrl.Manager) {
	mgr.GetWebhookServer().Register("/mutate-korifi-cloudfoundry-org-v1alpha1-cfapp-apprev", &admission.Webhook{
		Handler: r,
	})
	r.decoder = admission.NewDecoder(mgr.GetScheme())
}

func (r *AppRevWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {
	var cfApp korifiv1alpha1.CFApp
	if err := r.decoder.Decode(req, &cfApp); err != nil {
		return admission.Errored(http.StatusBadRequest, err)
	}

	var oldCFApp korifiv1alpha1.CFApp
	if err := r.decoder.DecodeRaw(req.OldObject, &oldCFApp); err != nil {
		return admission.Errored(http.StatusBadRequest, err)
	}

	if cfApp.Spec.DesiredState == korifiv1alpha1.StoppedState && oldCFApp.Spec.DesiredState == korifiv1alpha1.StartedState {
		newAppRev := bumpAppRev(cfApp.Annotations[korifiv1alpha1.CFAppRevisionKey])
		cfApp.Annotations[korifiv1alpha1.CFAppRevisionKey] = newAppRev
		cfApp.Annotations[korifiv1alpha1.CFAppLastStopRevisionKey] = newAppRev
	}

	marshalled, err := json.Marshal(cfApp)
	if err != nil {
		return admission.Errored(http.StatusInternalServerError, err)
	}

	return admission.PatchResponseFromRaw(req.Object.Raw, marshalled)
}

func bumpAppRev(currentRevValue string) string {
	revValue, err := strconv.Atoi(currentRevValue)
	if err != nil || revValue < 0 {
		apprevlog.V(1).Info("setting-invalid-app-rev-to-zero", "app-rev", currentRevValue)
		return korifiv1alpha1.CFAppDefaultRevision
	}

	return strconv.Itoa(revValue + 1)
}
