package store

import (
	"bytes"
	"encoding/binary"
	"encoding/gob"
	"fmt"

	"github.com/VictoriaMetrics/fastcache"
	"github.com/usememos/memos/api"
)

var (
	// 64 MiB.
	cacheSize                  = 1024 * 1024 * 64
	_         api.CacheService = (*CacheService)(nil)
)

// CacheService implements a cache.
type CacheService struct {
	cache *fastcache.Cache
}

// NewCacheService creates a cache service.
func NewCacheService() *CacheService {
	return &CacheService{
		cache: fastcache.New(cacheSize),
	}
}

// FindCache finds the value in cache.
func (s *CacheService) FindCache(namespace api.CacheNamespace, id int, entry interface{}) (bool, error) {
	buf1 := []byte{0, 0, 0, 0, 0, 0, 0, 0}
	binary.LittleEndian.PutUint64(buf1, uint64(id))

	buf2, has := s.cache.HasGet(nil, append([]byte(namespace), buf1...))
	if has {
		dec := gob.NewDecoder(bytes.NewReader(buf2))
		if err := dec.Decode(entry); err != nil {
			return false, fmt.Errorf("failed to decode entry for cache namespace: %s, error: %w", namespace, err)
		}
		return true, nil
	}

	return false, nil
}

// UpsertCache upserts the value to cache.
func (s *CacheService) UpsertCache(namespace api.CacheNamespace, id int, entry interface{}) error {
	buf1 := []byte{0, 0, 0, 0, 0, 0, 0, 0}
	binary.LittleEndian.PutUint64(buf1, uint64(id))

	var buf2 bytes.Buffer
	enc := gob.NewEncoder(&buf2)
	if err := enc.Encode(entry); err != nil {
		return fmt.Errorf("failed to encode entry for cache namespace: %s, error: %w", namespace, err)
	}
	s.cache.Set(append([]byte(namespace), buf1...), buf2.Bytes())

	return nil
}

// DeleteCache deletes the cache.
func (s *CacheService) DeleteCache(namespace api.CacheNamespace, id int) {
	buf1 := []byte{0, 0, 0, 0, 0, 0, 0, 0}
	binary.LittleEndian.PutUint64(buf1, uint64(id))

	_, has := s.cache.HasGet(nil, append([]byte(namespace), buf1...))
	if has {
		s.cache.Del(append([]byte(namespace), buf1...))
	}
}