package resource

import (
	"context"
	"net/http"
	"slices"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/grafana/grafana/pkg/apimachinery/utils"
	secrets "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
	"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)

// The "CanReference" check exists to avoid writing references to secrets
// the user should not allow granting access.  We only check it when the value changes
func canReferenceSecureValues(ctx context.Context,
	obj utils.GrafanaMetaAccessor,
	old utils.GrafanaMetaAccessor,
	secrets secrets.InlineSecureValueSupport,
) *resourcepb.ErrorResult {
	secure, err := obj.GetSecureValues()
	if err != nil || len(secure) == 0 {
		return AsErrorResult(err)
	}

	if secrets == nil {
		return &resourcepb.ErrorResult{
			Message: "secure storage not configured",
			Code:    http.StatusServiceUnavailable,
			Reason:  string(metav1.StatusReasonServiceUnavailable),
		}
	}

	// All references should only set a name
	names := make([]string, 0, len(secure))
	for k, v := range secure {
		if !v.Create.IsZero() {
			return newInvalidFieldError(obj,
				"unable to create values in unified storage",
				"secure", k, "create")
		}
		if v.Remove {
			return newInvalidFieldError(obj,
				"unable to save the remove command",
				"secure", k, "remove")
		}
		if v.Name == "" {
			return newRequiredFieldError(obj,
				"missing name",
				"secure", k, "name")
		}
		names = append(names, v.Name)
	}

	// This will call the real service to check access, converting any errors to protobuf
	canReference := func() *resourcepb.ErrorResult {
		slices.Sort(names)
		names = slices.Compact(names) //
		if err := secrets.CanReference(ctx, utils.ToObjectReference(obj), names...); err != nil {
			return AsErrorResult(err)
		}
		return nil
	}

	// Always check for create
	if old == nil {
		return canReference()
	}

	oldSecureValues, err := old.GetSecureValues()
	if err != nil || len(secure) != len(oldSecureValues) {
		return canReference()
	}
	for k, v := range secure {
		if oldSecureValues[k].Name != v.Name {
			return canReference()
		}
	}
	return nil // no need to check if the values are the same
}
