package resource

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"iter"
	"math/rand/v2"
	"net/http"
	"sort"
	"time"

	"github.com/bwmarrin/snowflake"
	"github.com/google/uuid"
	"github.com/prometheus/client_golang/prometheus"
	"go.opentelemetry.io/otel/trace"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

	"github.com/grafana/grafana/pkg/apimachinery/utils"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/storage/unified/resource/kv"
	"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
	"github.com/grafana/grafana/pkg/storage/unified/sql/db"
	"github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager"
	"github.com/grafana/grafana/pkg/util/debouncer"
)

const (
	defaultListBufferSize       = 100
	prunerMaxEvents             = 20
	defaultEventRetentionPeriod = 1 * time.Hour
	defaultEventPruningInterval = 5 * time.Minute
	clusterScopeNamespace       = "__cluster__"
)

// convertClusterNamespaceToEmpty converts the internal __cluster__ namespace back to empty string
// for cluster-scoped resources when returning to users
func convertClusterNamespaceToEmpty(namespace string) string {
	if namespace == clusterScopeNamespace {
		return ""
	}
	return namespace
}

// convertEmptyToClusterNamespace converts empty namespace to the internal __cluster__ namespace
// for cluster-scoped resources when WithExperimentalClusterScope is enabled
func convertEmptyToClusterNamespace(namespace string, withExperimentalClusterScope bool) string {
	if withExperimentalClusterScope && namespace == "" {
		return clusterScopeNamespace
	}
	return namespace
}

// kvStorageBackend Unified storage backend based on KV storage.
type kvStorageBackend struct {
	snowflake                    *snowflake.Node
	kv                           KV
	bulkLock                     *BulkLock
	dataStore                    *dataStore
	eventStore                   *eventStore
	notifier                     notifier
	log                          log.Logger
	withPruner                   bool
	eventRetentionPeriod         time.Duration
	eventPruningInterval         time.Duration
	historyPruner                Pruner
	withExperimentalClusterScope bool
	lastImportStore              *lastImportStore
	lastImportTimeMaxAge         time.Duration
	//tracer        trace.Tracer
	//reg           prometheus.Registerer

	rvManager *rvmanager.ResourceVersionManager

	// dbKeepAlive holds a reference to the database provider/connection owner to prevent it from being GC'd
	dbKeepAlive any
}

var _ KVBackend = &kvStorageBackend{}

type KVBackend interface {
	StorageBackend
	resourcepb.DiagnosticsServer
}

type KVBackendOptions struct {
	KvStore                      KV
	WithPruner                   bool
	WithExperimentalClusterScope bool                  // Allow empty namespace to be used for cluster-scoped resources.
	EventRetentionPeriod         time.Duration         // How long to keep events (default: 1 hour)
	EventPruningInterval         time.Duration         // How often to run the event pruning (default: 5 minutes)
	Tracer                       trace.Tracer          // TODO add tracing
	Reg                          prometheus.Registerer // TODO add metrics
	Log                          log.Logger

	UseChannelNotifier bool
	// Adding RvManager overrides the RV generated with snowflake in order to keep backwards compatibility with
	// unified/sql
	RvManager *rvmanager.ResourceVersionManager

	// dbKeepAlive holds a reference to the database provider/connection owner to prevent it from being GC'd
	// needed for sqlkv
	DBKeepAlive any

	// If not zero, the backend will regularly remove times from "last import times" older than this.
	LastImportTimeMaxAge time.Duration
}

func NewKVStorageBackend(opts KVBackendOptions) (KVBackend, error) {
	ctx := context.Background()
	kv := opts.KvStore

	logger := opts.Log
	if opts.Log == nil {
		logger = log.NewNopLogger()
	}

	s, err := snowflake.NewNode(rand.Int64N(1024))
	if err != nil {
		return nil, fmt.Errorf("failed to create snowflake node: %w", err)
	}
	eventStore := newEventStore(kv)

	eventRetentionPeriod := opts.EventRetentionPeriod
	if eventRetentionPeriod <= 0 {
		eventRetentionPeriod = defaultEventRetentionPeriod
	}

	eventPruningInterval := opts.EventPruningInterval
	if eventPruningInterval <= 0 {
		eventPruningInterval = defaultEventPruningInterval
	}

	backend := &kvStorageBackend{
		kv:                           kv,
		bulkLock:                     NewBulkLock(),
		dataStore:                    newDataStore(kv),
		eventStore:                   eventStore,
		notifier:                     newNotifier(eventStore, notifierOptions{log: logger, useChannelNotifier: opts.UseChannelNotifier}),
		snowflake:                    s,
		log:                          logger,
		eventRetentionPeriod:         eventRetentionPeriod,
		eventPruningInterval:         eventPruningInterval,
		withExperimentalClusterScope: opts.WithExperimentalClusterScope,
		rvManager:                    opts.RvManager,
		dbKeepAlive:                  opts.DBKeepAlive,
		lastImportStore:              newLastImportStore(kv),
		lastImportTimeMaxAge:         opts.LastImportTimeMaxAge,
	}
	err = backend.initPruner(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to initialize pruner: %w", err)
	}

	// Start the cleanup background job.
	go backend.runCleanups(ctx)

	logger.Info("backend initialized", "kv", fmt.Sprintf("%T", kv))

	return backend, nil
}

func (k *kvStorageBackend) IsHealthy(ctx context.Context, _ *resourcepb.HealthCheckRequest) (*resourcepb.HealthCheckResponse, error) {
	type pinger interface {
		Ping(context.Context) error
	}
	if p, ok := k.kv.(pinger); ok {
		if err := p.Ping(ctx); err != nil {
			return &resourcepb.HealthCheckResponse{Status: resourcepb.HealthCheckResponse_NOT_SERVING}, fmt.Errorf("KV store health check failed: %w", err)
		}
	}
	return &resourcepb.HealthCheckResponse{Status: resourcepb.HealthCheckResponse_SERVING}, nil
}

// runCleanups starts periodically cleans up old events and last import times.
func (k *kvStorageBackend) runCleanups(ctx context.Context) {
	// Run cleanup every hour
	ticker := time.NewTicker(k.eventPruningInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			k.log.Debug("Event cleanup stopped due to context cancellation")
			return
		case <-ticker.C:
			k.cleanupOldEvents(ctx)
			k.cleanupOldLastImportTimes(ctx)
		}
	}
}

func (k *kvStorageBackend) cleanupOldLastImportTimes(ctx context.Context) {
	if k.lastImportTimeMaxAge <= 0 {
		return
	}

	deleted, err := k.lastImportStore.CleanupLastImportTimes(ctx, k.lastImportTimeMaxAge)
	if err != nil {
		k.log.Error("Failed to cleanup last import times", "error", err)
	} else if deleted > 0 {
		k.log.Info("Cleaned up last import times", "deleted_count", deleted)
	}
}

// cleanupOldEvents performs the actual cleanup of old events
func (k *kvStorageBackend) cleanupOldEvents(ctx context.Context) {
	cutoff := time.Now().Add(-k.eventRetentionPeriod)
	deletedCount, err := k.eventStore.CleanupOldEvents(ctx, cutoff)
	if err != nil {
		k.log.Error("Failed to cleanup old events", "error", err)
		return
	}

	if deletedCount > 0 {
		k.log.Info("Cleaned up old events", "deleted_count", deletedCount, "retention_period", k.eventRetentionPeriod)
	}
}

func (k *kvStorageBackend) pruneEvents(ctx context.Context, key PruningKey) error {
	if !key.Validate() {
		return fmt.Errorf("invalid pruning key, all fields must be set: %+v", key)
	}

	counter := 0
	// iterate over all keys for the resource and delete versions beyond the latest 20
	for datakey, err := range k.dataStore.Keys(ctx, ListRequestKey{
		Namespace: key.Namespace,
		Group:     key.Group,
		Resource:  key.Resource,
		Name:      key.Name,
	}, SortOrderDesc) {
		if err != nil {
			return err
		}

		// Pruner needs to exclude deleted events
		if counter < prunerMaxEvents && datakey.Action != DataActionDeleted {
			counter++
			continue
		}

		// If we already have 20 versions, delete any more create or update events
		if datakey.Action != DataActionDeleted {
			err := k.dataStore.Delete(ctx, datakey)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (k *kvStorageBackend) initPruner(ctx context.Context) error {
	if !k.withPruner {
		k.log.Debug("Pruner disabled, using noop pruner")
		k.historyPruner = &NoopPruner{}
		return nil
	}

	k.log.Debug("Initializing history pruner")
	pruner, err := debouncer.NewGroup(debouncer.DebouncerOpts[PruningKey]{
		Name:           "history_pruner",
		BufferSize:     1000,
		MinWait:        time.Second * 30,
		MaxWait:        time.Minute * 5,
		ProcessHandler: k.pruneEvents,
		ErrorHandler: func(key PruningKey, err error) {
			k.log.Error("failed to prune history",
				"namespace", key.Namespace,
				"group", key.Group,
				"resource", key.Resource,
				"name", key.Name,
				"error", err)
		},
	})
	if err != nil {
		return err
	}

	k.historyPruner = pruner
	k.historyPruner.Start(ctx)
	return nil
}

// WriteEvent writes a resource event (create/update/delete) to the storage backend.
//
//nolint:gocyclo
func (k *kvStorageBackend) WriteEvent(ctx context.Context, event WriteEvent) (int64, error) {
	if err := event.Validate(); err != nil {
		return 0, fmt.Errorf("invalid event: %w", err)
	}

	rv := k.snowflake.Generate().Int64()

	namespace := convertEmptyToClusterNamespace(event.Key.Namespace, k.withExperimentalClusterScope)

	// When PreviousRV is not 0, fetch the latest resource and verify that the RV matches the PreviousRV
	if event.PreviousRV != 0 {
		latestKey, err := k.dataStore.GetLatestResourceKey(ctx, GetRequestKey{
			Group:     event.Key.Group,
			Resource:  event.Key.Resource,
			Namespace: namespace,
			Name:      event.Key.Name,
		})
		if err != nil {
			if errors.Is(err, ErrNotFound) {
				// Resource doesn't exist, but PreviousRV was provided
				return 0, fmt.Errorf("optimistic locking failed: resource not found")
			}
			return 0, fmt.Errorf("failed to fetch latest resource: %w", err)
		}

		// Verify the current RV matches the PreviousRV
		if latestKey.ResourceVersion != event.PreviousRV {
			return 0, fmt.Errorf("optimistic locking failed: requested RV %d does not match saved RV %d", event.PreviousRV, latestKey.ResourceVersion)
		}
	}

	obj := event.Object
	// Write data.
	var action kv.DataAction
	switch event.Type {
	case resourcepb.WatchEvent_ADDED:
		action = DataActionCreated
		// Check if resource already exists for create operations
		_, err := k.dataStore.GetLatestResourceKey(ctx, GetRequestKey{
			Group:     event.Key.Group,
			Resource:  event.Key.Resource,
			Namespace: namespace,
			Name:      event.Key.Name,
		})
		if err == nil {
			// Resource exists, return already exists error
			return 0, ErrResourceAlreadyExists
		}
		if !errors.Is(err, ErrNotFound) {
			// Some other error occurred
			return 0, fmt.Errorf("failed to check if resource exists: %w", err)
		}
	case resourcepb.WatchEvent_MODIFIED:
		action = DataActionUpdated
	case resourcepb.WatchEvent_DELETED:
		action = DataActionDeleted
		obj = event.ObjectOld
	default:
		return 0, fmt.Errorf("invalid event type: %d", event.Type)
	}

	if obj == nil {
		return 0, fmt.Errorf("object is nil")
	}

	// Write the data
	dataKey := DataKey{
		Group:           event.Key.Group,
		Resource:        event.Key.Resource,
		Namespace:       namespace,
		Name:            event.Key.Name,
		ResourceVersion: rv,
		Action:          action,
		Folder:          obj.GetFolder(),
	}

	if k.rvManager != nil {
		dataKey.GUID = uuid.New().String()
		var err error
		rv, err = k.rvManager.ExecWithRV(ctx, event.Key, func(tx db.Tx) (string, error) {
			if err := k.dataStore.Save(kv.ContextWithTx(ctx, tx), dataKey, bytes.NewReader(event.Value)); err != nil {
				return "", fmt.Errorf("failed to write data: %w", err)
			}

			if err := k.dataStore.applyBackwardsCompatibleChanges(ctx, tx, event, dataKey); err != nil {
				return "", fmt.Errorf("failed to apply backwards compatible updates: %w", err)
			}

			return dataKey.GUID, nil
		})
		if err != nil {
			return 0, fmt.Errorf("failed to write data: %w", err)
		}

		rv = rvmanager.SnowflakeFromRV(rv)
		dataKey.ResourceVersion = rv
	} else {
		err := k.dataStore.Save(ctx, dataKey, bytes.NewReader(event.Value))
		if err != nil {
			return 0, fmt.Errorf("failed to write data: %w", err)
		}
	}

	// Optimistic concurrency control to verify our write is the latest version
	// and that the resource still had the expected PreviousRV when we wrote it
	if event.PreviousRV != 0 {
		// Update operations: verify PreviousRV matches and our write is latest
		// Get both the latest and predecessor
		latestKey, prevKey, err := k.dataStore.GetLatestAndPredecessor(ctx, ListRequestKey{
			Group:     event.Key.Group,
			Resource:  event.Key.Resource,
			Namespace: namespace,
			Name:      event.Key.Name,
		})
		if err != nil {
			// If we can't read the latest version, clean up what we wrote
			_ = k.dataStore.Delete(ctx, dataKey)
			return 0, fmt.Errorf("failed to check latest version: %w", err)
		}

		// Check if the RV we just wrote is the latest. If not, a concurrent write with higher RV happened
		if latestKey.ResourceVersion != dataKey.ResourceVersion {
			// Delete the data we just wrote since it's not the latest
			_ = k.dataStore.Delete(ctx, dataKey)
			return 0, fmt.Errorf("optimistic locking failed: concurrent modification detected")
		}

		if !rvmanager.IsRvEqual(prevKey.ResourceVersion, event.PreviousRV) {
			// Another concurrent write happened between our read and write
			_ = k.dataStore.Delete(ctx, dataKey)
			return 0, fmt.Errorf("optimistic locking failed: resource was modified concurrently (expected previous RV %d, found %d)", event.PreviousRV, prevKey.ResourceVersion)
		}
	} else if event.Type == resourcepb.WatchEvent_ADDED {
		// Create operations: verify our write is the latest version
		latestKey, prevKey, err := k.dataStore.GetLatestAndPredecessor(ctx, ListRequestKey{
			Group:     event.Key.Group,
			Resource:  event.Key.Resource,
			Namespace: namespace,
			Name:      event.Key.Name,
		})
		if err != nil {
			// If we can't read the latest version, clean up what we wrote
			_ = k.dataStore.Delete(ctx, dataKey)
			return 0, fmt.Errorf("failed to check latest version: %w", err)
		}

		// Check if the RV we just wrote is the latest. If not, a concurrent create with higher RV happened
		if latestKey.ResourceVersion != dataKey.ResourceVersion {
			// Delete the data we just wrote since it's not the latest
			_ = k.dataStore.Delete(ctx, dataKey)
			return 0, fmt.Errorf("optimistic locking failed: concurrent create detected")
		}

		// Verify that the immediate predecessor is not a create
		if prevKey.Action == DataActionCreated {
			// Another concurrent create happened - delete our write and return error
			_ = k.dataStore.Delete(ctx, dataKey)
			return 0, fmt.Errorf("optimistic locking failed: concurrent create detected")
		}
	}

	// Write event
	eventData := Event{
		Namespace:       namespace,
		Group:           event.Key.Group,
		Resource:        event.Key.Resource,
		Name:            event.Key.Name,
		ResourceVersion: dataKey.ResourceVersion,
		Action:          action,
		Folder:          obj.GetFolder(),
		PreviousRV:      event.PreviousRV,
	}
	err := k.eventStore.Save(ctx, eventData)
	if err != nil {
		// Clean up the data we wrote since event save failed
		_ = k.dataStore.Delete(ctx, dataKey)
		return 0, fmt.Errorf("failed to save event: %w", err)
	}

	_ = k.historyPruner.Add(PruningKey{
		Namespace: namespace,
		Group:     event.Key.Group,
		Resource:  event.Key.Resource,
		Name:      event.Key.Name,
	})
	k.notifier.Publish(eventData)

	return rv, nil
}

func (k *kvStorageBackend) ReadResource(ctx context.Context, req *resourcepb.ReadRequest) *BackendReadResponse {
	if req.Key == nil {
		return &BackendReadResponse{Error: &resourcepb.ErrorResult{Code: http.StatusBadRequest, Message: "missing key"}}
	}

	namespace := convertEmptyToClusterNamespace(req.Key.Namespace, k.withExperimentalClusterScope)

	// If a specific resource version is requested, validate that it's not too high
	if req.ResourceVersion > 0 {
		// Fetch the latest RV
		latestRV := k.snowflake.Generate().Int64()
		if lastEventKey, err := k.eventStore.LastEventKey(ctx); err == nil {
			latestRV = lastEventKey.ResourceVersion
		} else if !errors.Is(err, ErrNotFound) {
			return &BackendReadResponse{Error: &resourcepb.ErrorResult{
				Code:    http.StatusInternalServerError,
				Message: fmt.Sprintf("failed to fetch latest resource version: %v", err),
			}}
		}

		// Check if the requested RV is higher than the latest available RV
		if req.ResourceVersion > latestRV {
			return &BackendReadResponse{
				Error: &resourcepb.ErrorResult{
					Code:    http.StatusGatewayTimeout,
					Reason:  string(metav1.StatusReasonTimeout), // match etcd behavior
					Message: "ResourceVersion is larger than max",
					Details: &resourcepb.ErrorDetails{
						Causes: []*resourcepb.ErrorCause{
							{
								Reason:  string(metav1.CauseTypeResourceVersionTooLarge),
								Message: fmt.Sprintf("requested: %d, current %d", req.ResourceVersion, latestRV),
							},
						},
					},
				},
			}
		}
	}

	meta, err := k.dataStore.GetResourceKeyAtRevision(ctx, GetRequestKey{
		Group:     req.Key.Group,
		Resource:  req.Key.Resource,
		Namespace: namespace,
		Name:      req.Key.Name,
	}, req.ResourceVersion)
	if errors.Is(err, ErrNotFound) {
		return &BackendReadResponse{Error: &resourcepb.ErrorResult{Code: http.StatusNotFound, Message: "not found"}}
	} else if err != nil {
		return &BackendReadResponse{Error: &resourcepb.ErrorResult{Code: http.StatusInternalServerError, Message: err.Error()}}
	}
	data, err := k.dataStore.Get(ctx, DataKey{
		Group:           req.Key.Group,
		Resource:        req.Key.Resource,
		Namespace:       namespace,
		Name:            req.Key.Name,
		ResourceVersion: meta.ResourceVersion,
		Action:          meta.Action,
		Folder:          meta.Folder,
	})
	if err != nil || data == nil {
		return &BackendReadResponse{Error: &resourcepb.ErrorResult{Code: http.StatusInternalServerError, Message: err.Error()}}
	}
	value, err := readAndClose(data)
	if err != nil {
		return &BackendReadResponse{Error: &resourcepb.ErrorResult{Code: http.StatusInternalServerError, Message: err.Error()}}
	}
	return &BackendReadResponse{
		Key:             req.Key,
		ResourceVersion: meta.ResourceVersion,
		Value:           value,
		Folder:          meta.Folder,
	}
}

// ListIterator returns an iterator for listing resources.
func (k *kvStorageBackend) ListIterator(ctx context.Context, req *resourcepb.ListRequest, cb func(ListIterator) error) (int64, error) {
	if req.Options == nil || req.Options.Key == nil {
		return 0, fmt.Errorf("missing options or key in ListRequest")
	}

	namespace := convertEmptyToClusterNamespace(req.Options.Key.Namespace, k.withExperimentalClusterScope)

	// Parse continue token if provided
	listOptions := ListRequestOptions{
		Key: ListRequestKey{
			Group:     req.Options.Key.Group,
			Resource:  req.Options.Key.Resource,
			Namespace: namespace,
			Name:      req.Options.Key.Name,
		},
		ResourceVersion: req.ResourceVersion,
	}

	if req.NextPageToken != "" {
		token, err := GetContinueToken(req.NextPageToken)
		if err != nil {
			return 0, fmt.Errorf("invalid continue token: %w", err)
		}
		if token.Name == "" {
			return 0, fmt.Errorf("invalid continue token: name is required for list resources")
		}
		// Only use token namespace for cross-namespace queries (when request namespace is empty)
		if req.Options.Key.Namespace == "" {
			listOptions.ContinueNamespace = token.Namespace
		}
		listOptions.ContinueName = token.Name
		listOptions.ResourceVersion = token.ResourceVersion
	}

	// We set the listRV to the last event resource version.
	// If no events exist yet, we generate a new snowflake.
	listRV := k.snowflake.Generate().Int64()
	if lastEventKey, err := k.eventStore.LastEventKey(ctx); err == nil {
		listRV = lastEventKey.ResourceVersion
	} else if !errors.Is(err, ErrNotFound) {
		return 0, fmt.Errorf("failed to fetch last event: %w", err)
	}

	if listOptions.ResourceVersion > 0 {
		listRV = listOptions.ResourceVersion
	}

	// Fetch the latest objects
	keys := make([]DataKey, 0, min(defaultListBufferSize, req.Limit+1))
	for dataKey, err := range k.dataStore.ListResourceKeysAtRevision(ctx, listOptions) {
		if err != nil {
			return 0, err
		}

		keys = append(keys, dataKey)
		// Only fetch the first limit items + 1 to get the next token.
		if req.Limit > 0 && len(keys) >= int(req.Limit+1) {
			break
		}
	}
	// Create pull-style iterator from BatchGet
	next, stop := iter.Pull2(k.dataStore.BatchGet(ctx, keys))
	defer stop()

	iter := kvListIterator{
		listRV:           listRV,
		isCrossNamespace: req.Options.Key.Namespace == "",
		next:             next,
	}
	err := cb(&iter)
	if err != nil {
		return 0, err
	}

	return listRV, nil
}

// kvListIterator implements ListIterator for KV storage
type kvListIterator struct {
	listRV           int64
	isCrossNamespace bool

	// pull-style iterator
	next func() (DataObj, error, bool)

	// current item state
	started        bool
	currentDataObj DataObj
	value          []byte
	err            error
	nextDataObj    DataObj
	nextErr        error
	hasMore        bool
}

func (i *kvListIterator) Next() bool {
	if !i.started {
		i.started = true

		i.nextDataObj, i.nextErr, i.hasMore = i.next()
	}

	if !i.hasMore {
		return false
	}

	i.currentDataObj, i.err = i.nextDataObj, i.nextErr
	if i.err != nil {
		return false
	}

	i.value, i.err = readAndClose(i.currentDataObj.Value)
	if i.err != nil {
		return false
	}

	i.nextDataObj, i.nextErr, i.hasMore = i.next()

	return true
}

func (i *kvListIterator) Error() error {
	return i.err
}

func (i *kvListIterator) ContinueToken() string {
	token := ContinueToken{
		Name:            i.nextDataObj.Key.Name,
		ResourceVersion: i.listRV,
	}
	// Only store namespace in token for cross-namespace queries
	if i.isCrossNamespace {
		token.Namespace = i.nextDataObj.Key.Namespace
	}
	return token.String()
}

func (i *kvListIterator) ResourceVersion() int64 {
	return i.currentDataObj.Key.ResourceVersion
}

func (i *kvListIterator) Namespace() string {
	return convertClusterNamespaceToEmpty(i.currentDataObj.Key.Namespace)
}

func (i *kvListIterator) Name() string {
	return i.currentDataObj.Key.Name
}

func (i *kvListIterator) Folder() string {
	return i.currentDataObj.Key.Folder
}

func (i *kvListIterator) Value() []byte {
	return i.value
}

func validateListHistoryRequest(req *resourcepb.ListRequest) error {
	if req.Options == nil || req.Options.Key == nil {
		return fmt.Errorf("missing options or key in ListRequest")
	}
	key := req.Options.Key
	if key.Group == "" {
		return fmt.Errorf("group is required")
	}
	if key.Resource == "" {
		return fmt.Errorf("resource is required")
	}
	if key.Namespace == "" {
		return fmt.Errorf("namespace is required")
	}
	return nil
}

// filterHistoryKeysByVersion filters history keys based on version match criteria
func filterHistoryKeysByVersion(historyKeys []DataKey, req *resourcepb.ListRequest) ([]DataKey, error) {
	switch req.GetVersionMatchV2() {
	case resourcepb.ResourceVersionMatchV2_Exact:
		if req.ResourceVersion <= 0 {
			return nil, fmt.Errorf("expecting an explicit resource version query when using Exact matching")
		}
		exactKeys := make([]DataKey, 0, len(historyKeys))
		for _, key := range historyKeys {
			if key.ResourceVersion == req.ResourceVersion {
				exactKeys = append(exactKeys, key)
			}
		}
		return exactKeys, nil
	case resourcepb.ResourceVersionMatchV2_NotOlderThan:
		if req.ResourceVersion > 0 {
			filteredKeys := make([]DataKey, 0, len(historyKeys))
			for _, key := range historyKeys {
				if key.ResourceVersion >= req.ResourceVersion {
					filteredKeys = append(filteredKeys, key)
				}
			}
			return filteredKeys, nil
		}
	default:
		if req.ResourceVersion > 0 {
			filteredKeys := make([]DataKey, 0, len(historyKeys))
			for _, key := range historyKeys {
				if key.ResourceVersion <= req.ResourceVersion {
					filteredKeys = append(filteredKeys, key)
				}
			}
			return filteredKeys, nil
		}
	}
	return historyKeys, nil
}

// applyLiveHistoryFilter applies "live" history logic by ignoring events before the last delete
func applyLiveHistoryFilter(filteredKeys []DataKey, req *resourcepb.ListRequest) []DataKey {
	useLatestDeletionAsMinRV := req.ResourceVersion == 0 && req.Source != resourcepb.ListRequest_TRASH && req.GetVersionMatchV2() != resourcepb.ResourceVersionMatchV2_Exact
	if !useLatestDeletionAsMinRV {
		return filteredKeys
	}

	latestDeleteRV := int64(0)
	for _, key := range filteredKeys {
		if key.Action == DataActionDeleted && key.ResourceVersion > latestDeleteRV {
			latestDeleteRV = key.ResourceVersion
		}
	}
	if latestDeleteRV > 0 {
		liveKeys := make([]DataKey, 0, len(filteredKeys))
		for _, key := range filteredKeys {
			if key.ResourceVersion > latestDeleteRV {
				liveKeys = append(liveKeys, key)
			}
		}
		return liveKeys
	}
	return filteredKeys
}

// sortByResourceVersion sorts the history keys based on the sortAscending flag
func sortByResourceVersion(filteredKeys []DataKey, sortAscending bool) {
	if sortAscending {
		sort.Slice(filteredKeys, func(i, j int) bool {
			return filteredKeys[i].ResourceVersion < filteredKeys[j].ResourceVersion
		})
	} else {
		sort.Slice(filteredKeys, func(i, j int) bool {
			return filteredKeys[i].ResourceVersion > filteredKeys[j].ResourceVersion
		})
	}
}

// applyPagination filters keys based on pagination parameters
func applyPagination(keys []DataKey, lastSeenRV int64, sortAscending bool) []DataKey {
	if lastSeenRV == 0 {
		return keys
	}

	pagedKeys := make([]DataKey, 0, len(keys))
	for _, key := range keys {
		if sortAscending && key.ResourceVersion > lastSeenRV {
			pagedKeys = append(pagedKeys, key)
		} else if !sortAscending && key.ResourceVersion < lastSeenRV {
			pagedKeys = append(pagedKeys, key)
		}
	}
	return pagedKeys
}

func (k *kvStorageBackend) ListModifiedSince(ctx context.Context, key NamespacedResource, sinceRv int64) (int64, iter.Seq2[*ModifiedResource, error]) {
	if !key.Valid() {
		return 0, func(yield func(*ModifiedResource, error) bool) {
			yield(nil, fmt.Errorf("group, resource, and namespace are required"))
		}
	}

	if sinceRv <= 0 {
		return 0, func(yield func(*ModifiedResource, error) bool) {
			yield(nil, fmt.Errorf("sinceRv must be greater than 0"))
		}
	}

	latestEvent, err := k.eventStore.LastEventKey(ctx)
	if err != nil {
		if errors.Is(err, ErrNotFound) {
			return sinceRv, func(yield func(*ModifiedResource, error) bool) { /* nothing to return */ }
		}

		return 0, func(yield func(*ModifiedResource, error) bool) {
			yield(nil, fmt.Errorf("error trying to retrieve last event key: %w", err))
		}
	}

	if latestEvent.ResourceVersion == sinceRv {
		return sinceRv, func(yield func(*ModifiedResource, error) bool) { /* nothing to return */ }
	}

	// Check if sinceRv is older than 1 hour
	sinceRvTimestamp := snowflake.ID(sinceRv).Time()
	sinceTime := time.Unix(0, sinceRvTimestamp*int64(time.Millisecond))
	sinceRvAge := time.Since(sinceTime)

	if sinceRvAge > time.Hour {
		k.log.Debug("ListModifiedSince using data store", "sinceRv", sinceRv, "sinceRvAge", sinceRvAge)
		return latestEvent.ResourceVersion, k.listModifiedSinceDataStore(ctx, key, sinceRv)
	}

	k.log.Debug("ListModifiedSince using event store", "sinceRv", sinceRv, "sinceRvAge", sinceRvAge)
	return latestEvent.ResourceVersion, k.listModifiedSinceEventStore(ctx, key, sinceRv)
}

func convertEventType(action kv.DataAction) resourcepb.WatchEvent_Type {
	switch action {
	case DataActionCreated:
		return resourcepb.WatchEvent_ADDED
	case DataActionUpdated:
		return resourcepb.WatchEvent_MODIFIED
	case DataActionDeleted:
		return resourcepb.WatchEvent_DELETED
	default:
		panic(fmt.Sprintf("unknown DataAction: %v", action))
	}
}

func (k *kvStorageBackend) getValueFromDataStore(ctx context.Context, dataKey DataKey) ([]byte, error) {
	raw, err := k.dataStore.Get(ctx, dataKey)
	if err != nil {
		return []byte{}, err
	}

	value, err := io.ReadAll(raw)
	if err != nil {
		return []byte{}, err
	}

	return value, nil
}

func (k *kvStorageBackend) listModifiedSinceDataStore(ctx context.Context, key NamespacedResource, sinceRv int64) iter.Seq2[*ModifiedResource, error] {
	return func(yield func(*ModifiedResource, error) bool) {
		var lastSeenResource *ModifiedResource
		var lastSeenDataKey DataKey
		for dataKey, err := range k.dataStore.Keys(ctx, ListRequestKey{Namespace: key.Namespace, Group: key.Group, Resource: key.Resource}, SortOrderAsc) {
			if err != nil {
				yield(&ModifiedResource{}, err)
				return
			}

			if dataKey.ResourceVersion < sinceRv {
				continue
			}

			if lastSeenResource == nil {
				lastSeenResource = &ModifiedResource{
					Key: resourcepb.ResourceKey{
						Namespace: dataKey.Namespace,
						Group:     dataKey.Group,
						Resource:  dataKey.Resource,
						Name:      dataKey.Name,
					},
					ResourceVersion: dataKey.ResourceVersion,
					Action:          convertEventType(dataKey.Action),
				}
				lastSeenDataKey = dataKey
			}

			if lastSeenResource.Key.Name != dataKey.Name {
				value, err := k.getValueFromDataStore(ctx, lastSeenDataKey)
				if err != nil {
					yield(&ModifiedResource{}, err)
					return
				}

				lastSeenResource.Value = value

				if !yield(lastSeenResource, nil) {
					return
				}
			}

			lastSeenResource = &ModifiedResource{
				Key: resourcepb.ResourceKey{
					Namespace: dataKey.Namespace,
					Group:     dataKey.Group,
					Resource:  dataKey.Resource,
					Name:      dataKey.Name,
				},
				ResourceVersion: dataKey.ResourceVersion,
				Action:          convertEventType(dataKey.Action),
			}
			lastSeenDataKey = dataKey
		}

		if lastSeenResource != nil {
			value, err := k.getValueFromDataStore(ctx, lastSeenDataKey)
			if err != nil {
				yield(&ModifiedResource{}, err)
				return
			}

			lastSeenResource.Value = value

			yield(lastSeenResource, nil)
		}
	}
}

func (k *kvStorageBackend) listModifiedSinceEventStore(ctx context.Context, key NamespacedResource, sinceRv int64) iter.Seq2[*ModifiedResource, error] {
	return func(yield func(*ModifiedResource, error) bool) {
		// we only care about the latest revision of every resource in the list
		seen := make(map[string]struct{})
		for evtKeyStr, err := range k.eventStore.ListKeysSince(ctx, subtractDurationFromSnowflake(sinceRv, defaultLookbackPeriod), SortOrderDesc) {
			if err != nil {
				yield(&ModifiedResource{}, err)
				return
			}

			evtKey, err := ParseEventKey(evtKeyStr)
			if err != nil {
				yield(&ModifiedResource{}, err)
				return
			}

			if evtKey.ResourceVersion < sinceRv {
				continue
			}

			if evtKey.Group != key.Group || evtKey.Resource != key.Resource || evtKey.Namespace != key.Namespace {
				continue
			}

			if _, ok := seen[evtKey.Name]; ok {
				continue
			}

			seen[evtKey.Name] = struct{}{}
			value, err := k.getValueFromDataStore(ctx, DataKey(evtKey))
			if err != nil {
				yield(&ModifiedResource{}, err)
				return
			}

			if !yield(&ModifiedResource{
				Key: resourcepb.ResourceKey{
					Group:     evtKey.Group,
					Resource:  evtKey.Resource,
					Namespace: evtKey.Namespace,
					Name:      evtKey.Name,
				},
				Action:          convertEventType(evtKey.Action),
				ResourceVersion: evtKey.ResourceVersion,
				Value:           value,
			}, nil) {
				return
			}
		}
	}
}

// ListHistory is like ListIterator, but it returns the history of a resource.
func (k *kvStorageBackend) ListHistory(ctx context.Context, req *resourcepb.ListRequest, fn func(ListIterator) error) (int64, error) {
	if err := validateListHistoryRequest(req); err != nil {
		return 0, err
	}
	key := req.Options.Key
	// Parse continue token if provided
	lastSeenRV := int64(0)
	sortAscending := req.GetVersionMatchV2() == resourcepb.ResourceVersionMatchV2_NotOlderThan
	if req.NextPageToken != "" {
		token, err := GetContinueToken(req.NextPageToken)
		if err != nil {
			return 0, fmt.Errorf("invalid continue token: %w", err)
		}
		lastSeenRV = token.ResourceVersion
		sortAscending = token.SortAscending
	}

	// Generate a new resource version for the list
	listRV := k.snowflake.Generate().Int64()

	// Get all history entries by iterating through datastore keys
	historyKeys := make([]DataKey, 0, min(defaultListBufferSize, req.Limit+1))

	// Use datastore.Keys to get all data keys for this specific resource
	for dataKey, err := range k.dataStore.Keys(ctx, ListRequestKey{
		Namespace: key.Namespace,
		Group:     key.Group,
		Resource:  key.Resource,
		Name:      key.Name,
	}, SortOrderAsc) {
		if err != nil {
			return 0, err
		}
		historyKeys = append(historyKeys, dataKey)
	}

	// Check if context has been cancelled
	if ctx.Err() != nil {
		return 0, ctx.Err()
	}

	// Handle trash differently from regular history
	if req.Source == resourcepb.ListRequest_TRASH {
		return k.processTrashEntries(ctx, req, fn, historyKeys, lastSeenRV, sortAscending, listRV)
	}

	// Apply filtering based on version match
	filteredKeys, filterErr := filterHistoryKeysByVersion(historyKeys, req)
	if filterErr != nil {
		return 0, filterErr
	}

	// Apply "live" history logic: ignore events before the last delete
	filteredKeys = applyLiveHistoryFilter(filteredKeys, req)

	// Sort the entries if not already sorted correctly
	sortByResourceVersion(filteredKeys, sortAscending)

	// Pagination: filter out items up to and including lastSeenRV
	pagedKeys := applyPagination(filteredKeys, lastSeenRV, sortAscending)

	// Create pull-style iterator from BatchGet
	next, stop := iter.Pull2(k.dataStore.BatchGet(ctx, pagedKeys))
	defer stop()

	iter := kvHistoryIterator{
		listRV:        listRV,
		sortAscending: sortAscending,
		next:          next,
	}

	err := fn(&iter)
	if err != nil {
		return 0, err
	}

	return listRV, nil
}

// processTrashEntries handles the special case of listing deleted items (trash)
func (k *kvStorageBackend) processTrashEntries(ctx context.Context, req *resourcepb.ListRequest, fn func(ListIterator) error, historyKeys []DataKey, lastSeenRV int64, sortAscending bool, listRV int64) (int64, error) {
	// Filter to only deleted entries
	deletedKeys := make([]DataKey, 0, len(historyKeys))
	for _, key := range historyKeys {
		if key.Action == DataActionDeleted {
			deletedKeys = append(deletedKeys, key)
		}
	}

	// Check if the resource currently exists (is live)
	// If it exists, don't return any trash entries
	_, err := k.dataStore.GetLatestResourceKey(ctx, GetRequestKey{
		Group:     req.Options.Key.Group,
		Resource:  req.Options.Key.Resource,
		Namespace: req.Options.Key.Namespace,
		Name:      req.Options.Key.Name,
	})

	trashKeys := make([]DataKey, 0, 1)
	if errors.Is(err, ErrNotFound) {
		// Resource doesn't exist currently, so we can return the latest delete
		// Find the latest delete event
		var latestDelete *DataKey
		for _, key := range deletedKeys {
			if latestDelete == nil || key.ResourceVersion > latestDelete.ResourceVersion {
				latestDelete = &key
			}
		}
		if latestDelete != nil {
			trashKeys = append(trashKeys, *latestDelete)
		}
	}
	// If err != ErrNotFound, the resource exists, so no trash entries should be returned

	// Apply version filtering
	filteredKeys, err := filterHistoryKeysByVersion(trashKeys, req)
	if err != nil {
		return 0, err
	}

	// Sort the entries
	sortByResourceVersion(filteredKeys, sortAscending)

	// Pagination: filter out items up to and including lastSeenRV
	pagedKeys := applyPagination(filteredKeys, lastSeenRV, sortAscending)

	// Create pull-style iterator from BatchGet
	next, stop := iter.Pull2(k.dataStore.BatchGet(ctx, pagedKeys))
	defer stop()

	iter := kvHistoryIterator{
		listRV:          listRV,
		sortAscending:   sortAscending,
		skipProvisioned: true,
		next:            next,
	}

	err = fn(&iter)
	if err != nil {
		return 0, err
	}

	return listRV, nil
}

// kvHistoryIterator implements ListIterator for KV storage history
type kvHistoryIterator struct {
	listRV          int64
	sortAscending   bool
	skipProvisioned bool

	// pull-style iterator
	next func() (DataObj, error, bool)

	// current item state
	currentDataObj *DataObj
	value          []byte
	folder         string
	err            error
}

func (i *kvHistoryIterator) Next() bool {
	// Pull next item from the iterator
	dataObj, err, ok := i.next()
	if !ok {
		return false
	}
	if err != nil {
		i.err = err
		return false
	}

	i.currentDataObj = &dataObj

	i.value, err = readAndClose(dataObj.Value)
	if err != nil {
		i.err = err
		return false
	}

	// Extract the folder from the meta data
	partial := &metav1.PartialObjectMetadata{}
	err = json.Unmarshal(i.value, partial)
	if err != nil {
		i.err = err
		return false
	}

	meta, err := utils.MetaAccessor(partial)
	if err != nil {
		i.err = err
		return false
	}
	i.folder = meta.GetFolder()

	// if the resource is provisioned and we are skipping provisioned resources, continue onto the next one
	if i.skipProvisioned && meta.GetAnnotation(utils.AnnoKeyManagerKind) != "" {
		return i.Next()
	}

	return true
}

func (i *kvHistoryIterator) Error() error {
	return i.err
}

func (i *kvHistoryIterator) ContinueToken() string {
	if i.currentDataObj == nil {
		return ""
	}
	rv := i.currentDataObj.Key.ResourceVersion
	token := ContinueToken{
		ResourceVersion: rv,
		SortAscending:   i.sortAscending,
	}
	return token.String()
}

func (i *kvHistoryIterator) ResourceVersion() int64 {
	if i.currentDataObj != nil {
		return i.currentDataObj.Key.ResourceVersion
	}
	return 0
}

func (i *kvHistoryIterator) Namespace() string {
	if i.currentDataObj != nil {
		return i.currentDataObj.Key.Namespace
	}
	return ""
}

func (i *kvHistoryIterator) Name() string {
	if i.currentDataObj != nil {
		return i.currentDataObj.Key.Name
	}
	return ""
}

func (i *kvHistoryIterator) Folder() string {
	return i.folder
}

func (i *kvHistoryIterator) Value() []byte {
	return i.value
}

// WatchWriteEvents returns a channel that receives write events.
func (k *kvStorageBackend) WatchWriteEvents(ctx context.Context) (<-chan *WrittenEvent, error) {
	// Create a channel to receive events
	events := make(chan *WrittenEvent, 10000) // TODO: make this configurable

	notifierEvents := k.notifier.Watch(ctx, defaultWatchOptions())
	go func() {
		for event := range notifierEvents {
			// fetch the data
			dataReader, err := k.dataStore.Get(ctx, DataKey{
				Group:           event.Group,
				Resource:        event.Resource,
				Namespace:       event.Namespace,
				Name:            event.Name,
				ResourceVersion: event.ResourceVersion,
				Action:          event.Action,
				Folder:          event.Folder,
			})
			if err != nil || dataReader == nil {
				k.log.Error("failed to get data for event", "error", err)
				continue
			}
			data, err := readAndClose(dataReader)
			if err != nil {
				k.log.Error("failed to read and close data for event", "error", err)
				continue
			}
			var t resourcepb.WatchEvent_Type
			switch event.Action {
			case DataActionCreated:
				t = resourcepb.WatchEvent_ADDED
			case DataActionUpdated:
				t = resourcepb.WatchEvent_MODIFIED
			case DataActionDeleted:
				t = resourcepb.WatchEvent_DELETED
			}

			events <- &WrittenEvent{
				Key: &resourcepb.ResourceKey{
					Namespace: convertClusterNamespaceToEmpty(event.Namespace),
					Group:     event.Group,
					Resource:  event.Resource,
					Name:      event.Name,
				},
				Type:            t,
				Folder:          event.Folder,
				Value:           data,
				ResourceVersion: event.ResourceVersion,
				PreviousRV:      event.PreviousRV,
				Timestamp:       event.ResourceVersion / time.Second.Nanoseconds(), // convert to seconds
			}
		}
		close(events)
	}()
	return events, nil
}

// GetResourceStats returns resource stats within the storage backend.
func (k *kvStorageBackend) GetResourceStats(ctx context.Context, nsr NamespacedResource, minCount int) ([]ResourceStats, error) {
	return k.dataStore.GetResourceStats(ctx, nsr.Namespace, minCount)
}

func (k *kvStorageBackend) GetResourceLastImportTimes(ctx context.Context) iter.Seq2[ResourceLastImportTime, error] {
	return func(yield func(ResourceLastImportTime, error) bool) {
		valid, _, err := k.lastImportStore.ListLastImportTimes(ctx, k.lastImportTimeMaxAge)
		if err != nil {
			yield(ResourceLastImportTime{}, err)
			return
		}
		for _, v := range valid {
			if !yield(v.ToResourceLastImportTime(), nil) {
				return
			}
		}
	}
}

func (b *kvStorageBackend) ProcessBulk(ctx context.Context, setting BulkSettings, iter BulkRequestIterator) *resourcepb.BulkResponse {
	// TODO cross-node lock
	err := b.bulkLock.Start(setting.Collection)
	if err != nil {
		return &resourcepb.BulkResponse{
			Error: AsErrorResult(err),
		}
	}
	defer b.bulkLock.Finish(setting.Collection)

	bulkRvGenerator := newBulkRV()
	summaries := make(map[string]*resourcepb.BulkResponse_Summary, len(setting.Collection))
	rsp := &resourcepb.BulkResponse{}

	for _, key := range setting.Collection {
		events := make([]string, 0)
		for evtKeyStr, err := range b.eventStore.ListKeysSince(ctx, 1, SortOrderAsc) {
			if err != nil {
				b.log.Error("failed to list event: %s", err)
				return rsp
			}

			evtKey, err := ParseEventKey(evtKeyStr)
			if err != nil {
				b.log.Error("error parsing event key: %s", err)
				return rsp
			}

			if evtKey.Group != key.Group || evtKey.Resource != key.Resource || evtKey.Namespace != key.Namespace {
				continue
			}

			events = append(events, evtKeyStr)
		}

		if err := b.eventStore.batchDelete(ctx, events); err != nil {
			b.log.Error("failed to delete events: %s", err)
			return rsp
		}

		historyKeys := make([]DataKey, 0)

		for dataKey, err := range b.dataStore.Keys(ctx, ListRequestKey{
			Namespace: key.Namespace,
			Group:     key.Group,
			Resource:  key.Resource,
		}, SortOrderAsc) {
			if err != nil {
				b.log.Error("failed to list collection before delete: %s", err)
				return rsp
			}

			historyKeys = append(historyKeys, dataKey)
		}

		previousCount := int64(len(historyKeys))
		if err := b.dataStore.batchDelete(ctx, historyKeys); err != nil {
			b.log.Error("failed to delete collection: %s", err)
			return rsp
		}
		summaries[NSGR(key)] = &resourcepb.BulkResponse_Summary{
			Namespace:     key.Namespace,
			Group:         key.Group,
			Resource:      key.Resource,
			PreviousCount: previousCount,
		}
	}

	saved := make([]DataKey, 0)
	rollback := func() {
		// we don't have transactions in the kv store, so we simply delete everything we created
		err = b.dataStore.batchDelete(ctx, saved)
		if err != nil {
			b.log.Error("failed to delete during rollback: %s", err)
		}
	}

	updatedResources := make(map[NamespacedResource]bool)

	for iter.Next() {
		if iter.RollbackRequested() {
			rollback()
			break
		}

		req := iter.Request()
		if req == nil {
			rollback()
			rsp.Error = AsErrorResult(fmt.Errorf("missing request"))
			break
		}

		rsp.Processed++

		var action kv.DataAction
		switch resourcepb.WatchEvent_Type(req.Action) {
		case resourcepb.WatchEvent_ADDED:
			action = DataActionCreated
			// Check if resource already exists for create operations
			_, err := b.dataStore.GetLatestResourceKey(ctx, GetRequestKey{
				Group:     req.Key.Group,
				Resource:  req.Key.Resource,
				Namespace: req.Key.Namespace,
				Name:      req.Key.Name,
			})
			if err == nil {
				rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{
					Key:    req.Key,
					Action: req.Action,
					Error:  "resource already exists",
				})
				continue
			}
			if !errors.Is(err, ErrNotFound) {
				rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{
					Key:    req.Key,
					Action: req.Action,
					Error:  fmt.Sprintf("failed to check if resource exists: %s", err),
				})
				continue
			}
		case resourcepb.WatchEvent_MODIFIED:
			action = DataActionUpdated
		case resourcepb.WatchEvent_DELETED:
			action = DataActionDeleted
		default:
			rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{
				Key:    req.Key,
				Action: req.Action,
				Error:  "invalid event type",
			})
			continue
		}

		obj := &unstructured.Unstructured{}
		err := obj.UnmarshalJSON(req.Value)
		if err != nil {
			rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{
				Key:    req.Key,
				Action: req.Action,
				Error:  "unable to unmarshal json",
			})
			continue
		}

		dataKey := DataKey{
			Group:           req.Key.Group,
			Resource:        req.Key.Resource,
			Namespace:       req.Key.Namespace,
			Name:            req.Key.Name,
			ResourceVersion: bulkRvGenerator.next(obj),
			Action:          action,
			Folder:          req.Folder,
		}
		err = b.dataStore.Save(ctx, dataKey, bytes.NewReader(req.Value))
		if err != nil {
			rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{
				Key:    req.Key,
				Action: req.Action,
				Error:  fmt.Sprintf("failed to save resource: %s", err),
			})
			continue
		}

		saved = append(saved, dataKey)
		updatedResources[NamespacedResource{Namespace: dataKey.Namespace, Group: dataKey.Group, Resource: dataKey.Resource}] = true
	}

	if rsp.Error == nil {
		now := time.Now()
		for nsr := range updatedResources {
			err := b.lastImportStore.Save(ctx, ResourceLastImportTime{
				NamespacedResource: nsr,
				LastImportTime:     now,
			})
			if err != nil {
				rsp.Error = AsErrorResult(fmt.Errorf("failed to save last import time for resource %s: %s", nsr.String(), err))
			}
		}
	}

	return rsp
}

// readAndClose reads all data from a ReadCloser and ensures it's closed,
// combining any errors from both operations.
func readAndClose(r io.ReadCloser) ([]byte, error) {
	data, err := io.ReadAll(r)
	return data, errors.Join(err, r.Close())
}
