// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package resourcepool

import (
	"context"

	"github.com/go-logr/logr"
	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/api/resource"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

	capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
	ad "github.com/projectcapsule/capsule/pkg/runtime/admission"
	"github.com/projectcapsule/capsule/pkg/runtime/events"
	"github.com/projectcapsule/capsule/pkg/runtime/handlers"
)

type poolValidationHandler struct {
	log logr.Logger
}

func PoolValidationHandler(log logr.Logger) handlers.Handler {
	return &poolValidationHandler{log: log}
}

func (h *poolValidationHandler) OnCreate(
	client.Client,
	client.Reader,
	admission.Decoder,
	events.EventRecorder,
) handlers.Func {
	return func(context.Context, admission.Request) *admission.Response {
		return nil
	}
}

func (h *poolValidationHandler) OnDelete(
	client.Client,
	client.Reader,
	admission.Decoder,
	events.EventRecorder,
) handlers.Func {
	return func(context.Context, admission.Request) *admission.Response {
		return nil
	}
}

func (h *poolValidationHandler) OnUpdate(
	_ client.Client,
	_ client.Reader,
	decoder admission.Decoder,
	_ events.EventRecorder,
) handlers.Func {
	return func(_ context.Context, req admission.Request) *admission.Response {
		oldPool := &capsulev1beta2.ResourcePool{}
		if err := decoder.DecodeRaw(req.OldObject, oldPool); err != nil {
			return ad.ErroredResponse(err)
		}

		pool := &capsulev1beta2.ResourcePool{}
		if err := decoder.Decode(req, pool); err != nil {
			return ad.ErroredResponse(err)
		}

		// Verify if resource decrease is allowed or no
		if !equality.Semantic.DeepEqual(pool.Spec.Quota.Hard, oldPool.Spec.Quota.Hard) {
			zeroValue := resource.MustParse("0")

			for resourceName, qt := range oldPool.Status.Allocation.Claimed {
				allocation, exists := pool.Spec.Quota.Hard[resourceName]

				if !exists {
					// May remove resources when unused
					if zeroValue.Cmp(qt) == 0 {
						continue
					}

					return ad.Denyf(
						"can not remove resource %s as it is still being allocated. Remove corresponding claims or keep the resources in the pool",
						resourceName,
					)
				}

				if allocation.Cmp(qt) < 0 {
					return ad.Denyf(
						"can not reduce %s usage to %s because quantity %s is claimed . Remove corresponding claims or keep the resources in the pool",
						resourceName,
						allocation.String(),
						qt.String(),
					)
				}
			}
		}

		return nil
	}
}
