package apikey

import (
	lru "github.com/hashicorp/golang-lru"
	portainer "github.com/portainer/portainer/api"
)

const defaultAPIKeyCacheSize = 1024

// entry is a tuple containing the user and API key associated to an API key digest
type entry struct {
	user   portainer.User
	apiKey portainer.APIKey
}

// apiKeyCache is a concurrency-safe, in-memory cache which primarily exists for to reduce database roundtrips.
// We store the api-key digest (keys) and the associated user and key-data (values) in the cache.
// This is required because HTTP requests will contain only the api-key digest in the x-api-key request header;
// digest value must be mapped to a portainer user (and respective key data) for validation.
// This cache is used to avoid multiple database queries to retrieve these user/key associated to the digest.
type apiKeyCache struct {
	// cache type [string]entry cache (key: string(digest), value: user/key entry)
	// note: []byte keys are not supported by golang-lru Cache
	cache *lru.Cache
}

// NewAPIKeyCache creates a new cache for API keys
func NewAPIKeyCache(cacheSize int) *apiKeyCache {
	cache, _ := lru.New(cacheSize)
	return &apiKeyCache{cache: cache}
}

// Get returns the user/key associated to an api-key's digest
// This is required because HTTP requests will contain the digest of the API key in header,
// the digest value must be mapped to a portainer user.
func (c *apiKeyCache) Get(digest []byte) (portainer.User, portainer.APIKey, bool) {
	val, ok := c.cache.Get(string(digest))
	if !ok {
		return portainer.User{}, portainer.APIKey{}, false
	}
	tuple := val.(entry)

	return tuple.user, tuple.apiKey, true
}

// Set persists a user/key entry to the cache
func (c *apiKeyCache) Set(digest []byte, user portainer.User, apiKey portainer.APIKey) {
	c.cache.Add(string(digest), entry{
		user:   user,
		apiKey: apiKey,
	})
}

// Delete evicts a digest's user/key entry key from the cache
func (c *apiKeyCache) Delete(digest []byte) {
	c.cache.Remove(string(digest))
}

// InvalidateUserKeyCache loops through all the api-keys associated to a user and removes them from the cache
func (c *apiKeyCache) InvalidateUserKeyCache(userId portainer.UserID) bool {
	present := false
	for _, k := range c.cache.Keys() {
		user, _, _ := c.Get([]byte(k.(string)))
		if user.ID == userId {
			present = c.cache.Remove(k)
		}
	}
	return present
}