package cache

import (
	"context"
	"github.com/fthvgb1/wp-go/safety"
	"sync"
	"time"
)

type MemoryMapCache[K comparable, V any] struct {
	*safety.Map[K, mapVal[V]]
}

func NewMemoryMapCacheByFn[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] {
	return &MapCache[K, V]{
		data:       NewMemoryMapCache[K, V](),
		cacheFunc:  fn,
		expireTime: expireTime,
		mux:        sync.Mutex{},
	}
}
func NewMemoryMapCacheByBatchFn[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] {
	r := &MapCache[K, V]{
		data:         NewMemoryMapCache[K, V](),
		batchCacheFn: fn,
		expireTime:   expireTime,
		mux:          sync.Mutex{},
	}
	r.setCacheFn(fn)
	return r
}

func NewMemoryMapCache[K comparable, V any]() *MemoryMapCache[K, V] {
	return &MemoryMapCache[K, V]{Map: safety.NewMap[K, mapVal[V]]()}
}

type mapVal[T any] struct {
	setTime time.Time
	ver     int
	data    T
}

func (m *MemoryMapCache[K, V]) Get(_ context.Context, key K) (r V, ok bool) {
	v, ok := m.Load(key)
	if ok {
		return v.data, true
	}
	return
}

func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V, _ time.Duration) {
	v, ok := m.Load(key)
	t := time.Now()
	if ok {
		v.data = val
		v.setTime = t
		v.ver++
	} else {
		v = mapVal[V]{
			setTime: t,
			ver:     1,
			data:    val,
		}
	}
	m.Store(key, v)
}

func (m *MemoryMapCache[K, V]) Ttl(_ context.Context, key K, expire time.Duration) time.Duration {
	v, ok := m.Load(key)
	if !ok {
		return time.Duration(-1)
	}
	return expire - time.Now().Sub(v.setTime)
}

func (m *MemoryMapCache[K, V]) Ver(_ context.Context, key K) int {
	v, ok := m.Load(key)
	if !ok {
		return -1
	}
	return v.ver
}

func (m *MemoryMapCache[K, V]) Flush(context.Context) {
	m.Map.Flush()
}

func (m *MemoryMapCache[K, V]) Delete(_ context.Context, key K) {
	m.Map.Delete(key)
}

func (m *MemoryMapCache[K, V]) ClearExpired(_ context.Context, expire time.Duration) {
	now := time.Duration(time.Now().UnixNano())

	m.Range(func(k K, v mapVal[V]) bool {
		if now > time.Duration(v.setTime.UnixNano())+expire {
			m.Map.Delete(k)
		}
		return true
	})
}
