package cache

import (
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/OrlovEvgeny/go-mcache"
	"github.com/rs/zerolog/log"
)

type Cache struct {
	mcache      *mcache.CacheDriver
	memoryLimit int
	lastCheck   time.Time
}

// NewInMemoryCache returns a new mcache that can grow infinitely.
func NewInMemoryCache() ICache {
	return mcache.New()
}

// NewInMemoryCache returns a new mcache with a memory limit.
// If the limit is exceeded, the cache will be cleared.
func NewInMemoryCacheWithLimit(memoryLimit int) ICache {
	return &Cache{
		mcache:      mcache.New(),
		memoryLimit: memoryLimit,
	}
}

func (c *Cache) Set(key string, value interface{}, ttl time.Duration) error {
	now := time.Now()

	// we don't want to do it too often
	if now.Sub(c.lastCheck) > (time.Second * 3) {
		if c.memoryLimitOvershot() {
			log.Info().Msg("[cache] memory limit exceeded, clearing cache")
			c.mcache.Truncate()
		}
		c.lastCheck = now
	}

	return c.mcache.Set(key, value, ttl)
}

func (c *Cache) Get(key string) (interface{}, bool) {
	return c.mcache.Get(key)
}

func (c *Cache) Remove(key string) {
	c.mcache.Remove(key)
}

func (c *Cache) memoryLimitOvershot() bool {
	mem := getCurrentMemory()

	log.Debug().Int("kB", mem).Msg("[cache] current memory usage")

	return mem > c.memoryLimit
}

// getCurrentMemory returns the current memory in KB
func getCurrentMemory() int {
	b, err := os.ReadFile("/proc/self/statm")
	if err != nil {
		log.Error().Err(err).Msg("[cache] could not read /proc/self/statm")
		return 0
	}

	str := string(b)
	arr := strings.Split(str, " ")

	// convert to pages
	res, err := strconv.Atoi(arr[1])
	if err != nil {
		log.Error().Err(err).Msg("[cache] could not convert string to int")
		return 0
	}

	// convert to KB
	return (res * os.Getpagesize()) / 1024
}