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

package resourcepool

import (
	"context"
	"fmt"
	"reflect"

	"github.com/go-logr/logr"
	"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 claimValidationHandler struct {
	log logr.Logger
}

func ClaimValidationHandler(log logr.Logger) handlers.Handler {
	return &claimValidationHandler{log: log}
}

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

func (h *claimValidationHandler) OnDelete(
	_ client.Client,
	_ client.Reader,
	decoder admission.Decoder,
	_ events.EventRecorder,
) handlers.Func {
	return func(_ context.Context, req admission.Request) *admission.Response {
		claim := &capsulev1beta2.ResourcePoolClaim{}

		if err := decoder.DecodeRaw(req.OldObject, claim); err != nil {
			return ad.ErroredResponse(fmt.Errorf("failed to decode old object: %w", err))
		}

		if claim.IsBoundInResourcePool() {
			return ad.Denyf("cannot delete the pool while claim is used in resourcepool %s", claim.Status.Pool.Name)
		}

		return nil
	}
}

func (h *claimValidationHandler) OnUpdate(
	_ client.Client,
	_ client.Reader,
	decoder admission.Decoder,
	_ events.EventRecorder,
) handlers.Func {
	return func(_ context.Context, req admission.Request) *admission.Response {
		oldClaim := &capsulev1beta2.ResourcePoolClaim{}
		newClaim := &capsulev1beta2.ResourcePoolClaim{}

		if err := decoder.DecodeRaw(req.OldObject, oldClaim); err != nil {
			return ad.ErroredResponse(fmt.Errorf("failed to decode old object: %w", err))
		}

		if err := decoder.Decode(req, newClaim); err != nil {
			return ad.ErroredResponse(fmt.Errorf("failed to decode new object: %w", err))
		}

		if oldClaim.IsBoundInResourcePool() {
			if oldClaim.Spec.Pool != newClaim.Spec.Pool || !reflect.DeepEqual(oldClaim.Spec.ResourceClaims, newClaim.Spec.ResourceClaims) {
				return ad.Denyf("cannot change the requested resources while claim is allocated to a resourcepool %s", oldClaim.Status.Pool.Name)
			}
		}

		return nil
	}
}
