// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package cache

import (
	"sync"

	"github.com/hashicorp/consul/internal/controller/cache/index"
	"github.com/hashicorp/consul/internal/controller/cache/indexers"
	"github.com/hashicorp/consul/proto-public/pbresource"
)

const IDIndex = "id"

type kindIndices struct {
	mu sync.RWMutex

	it unversionedType

	indices map[string]*index.IndexedData
}

func newKindIndices() *kindIndices {
	kind := &kindIndices{
		indices: make(map[string]*index.IndexedData),
	}

	// add the id index
	kind.indices[IDIndex] = indexers.IDIndex(IDIndex, index.IndexRequired).IndexedData()

	return kind
}

func (k *kindIndices) addIndex(i *index.Index) error {
	_, found := k.indices[i.Name()]
	if found {
		return DuplicateIndexError{name: i.Name()}
	}

	k.indices[i.Name()] = i.IndexedData()
	return nil
}

func (k *kindIndices) get(indexName string, args ...any) (*pbresource.Resource, error) {
	k.mu.RLock()
	defer k.mu.RUnlock()

	idx, err := k.getIndex(indexName)
	if err != nil {
		return nil, err
	}

	r, err := idx.Txn().Get(args...)
	if err != nil {
		return nil, IndexError{err: err, name: indexName}
	}
	return r, nil
}

func (k *kindIndices) listIterator(indexName string, args ...any) (ResourceIterator, error) {
	k.mu.RLock()
	defer k.mu.RUnlock()

	idx, err := k.getIndex(indexName)
	if err != nil {
		return nil, err
	}

	iter, err := idx.Txn().ListIterator(args...)
	if err != nil {
		return nil, IndexError{err: err, name: indexName}
	}
	return iter, nil
}

func (k *kindIndices) parentsIterator(indexName string, args ...any) (ResourceIterator, error) {
	k.mu.RLock()
	defer k.mu.RUnlock()
	idx, err := k.getIndex(indexName)
	if err != nil {
		return nil, err
	}

	iter, err := idx.Txn().ParentsIterator(args...)
	if err != nil {
		return nil, IndexError{err: err, name: indexName}
	}
	return iter, nil
}

func (k *kindIndices) insert(r *pbresource.Resource) error {
	k.mu.Lock()
	defer k.mu.Unlock()

	idx, err := k.getIndex(IDIndex)
	if err != nil {
		return err
	}

	existing, err := idx.Txn().Get(r.Id)
	if err != nil {
		return err
	}

	commit := false
	for name, idx := range k.indices {
		txn := idx.Txn()

		// Delete the previous version of the resource from the index.
		if existing != nil {
			if err := txn.Delete(existing); err != nil {
				return IndexError{name: name, err: err}
			}
		}

		// Now insert the new version into the index.
		err := txn.Insert(r)
		if err != nil {
			return IndexError{name: name, err: err}
		}
		// commit all radix trees once we know all index applies were successful. This is
		// still while holding the big write lock for this resource type so the order that
		// the radix tree updates occur shouldn't matter.
		defer func() {
			if commit {
				txn.Commit()
			}
		}()
	}

	// set commit to true so that the deferred funcs will commit all the radix tree transactions
	commit = true

	return nil
}

func (k *kindIndices) delete(r *pbresource.Resource) error {
	k.mu.Lock()
	defer k.mu.Unlock()

	idx, err := k.getIndex(IDIndex)
	if err != nil {
		return err
	}

	idTxn := idx.Txn()

	existing, err := idTxn.Get(r.Id)
	if err != nil {
		return err
	}
	if existing == nil {
		return nil
	}

	commit := false
	for name, idx := range k.indices {
		txn := idx.Txn()

		if err := txn.Delete(existing); err != nil {
			return IndexError{name: name, err: err}
		}

		// commit all radix trees once we know all index applies were successful. This is
		// still while holding the big write lock for this resource type so the order that
		// the radix tree updates occur shouldn't matter.
		defer func() {
			if commit {
				txn.Commit()
			}
		}()
	}

	// set commit to true so that the deferred txn commits will get executed
	commit = true

	return nil
}

func (k *kindIndices) getIndex(name string) (*index.IndexedData, error) {
	idx, ok := k.indices[name]
	if !ok {
		return nil, CacheTypeError{err: IndexNotFoundError{name: name}, it: k.it}
	}

	return idx, nil
}
