package cache_utils

import (
	"context"
	"encoding/json"
	"time"

	"github.com/valkey-io/valkey-go"
)

const (
	DefaultCacheTimeout = 10 * time.Second
	DefaultCacheExpiry  = 10 * time.Minute
	DefaultQueueTimeout = 30 * time.Second
)

type CacheUtil[T any] struct {
	client  valkey.Client
	prefix  string
	timeout time.Duration
	expiry  time.Duration
}

func NewCacheUtil[T any](client valkey.Client, prefix string) *CacheUtil[T] {
	return &CacheUtil[T]{
		client:  client,
		prefix:  prefix,
		timeout: DefaultCacheTimeout,
		expiry:  DefaultCacheExpiry,
	}
}

func (c *CacheUtil[T]) Get(key string) *T {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()

	fullKey := c.prefix + key
	result := c.client.Do(ctx, c.client.B().Get().Key(fullKey).Build())

	if result.Error() != nil {
		return nil
	}

	data, err := result.AsBytes()
	if err != nil {
		return nil
	}

	var item T
	if err := json.Unmarshal(data, &item); err != nil {
		return nil
	}

	return &item
}

func (c *CacheUtil[T]) Set(key string, item *T) {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()

	data, err := json.Marshal(item)
	if err != nil {
		return
	}

	fullKey := c.prefix + key
	c.client.Do(ctx, c.client.B().Set().Key(fullKey).Value(string(data)).Ex(c.expiry).Build())
}

func (c *CacheUtil[T]) SetWithExpiration(key string, item *T, expiry time.Duration) {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()

	data, err := json.Marshal(item)
	if err != nil {
		return
	}

	fullKey := c.prefix + key
	c.client.Do(ctx, c.client.B().Set().Key(fullKey).Value(string(data)).Ex(expiry).Build())
}

func (c *CacheUtil[T]) GetAndDelete(key string) *T {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()

	fullKey := c.prefix + key
	result := c.client.Do(ctx, c.client.B().Getdel().Key(fullKey).Build())

	if result.Error() != nil {
		return nil
	}

	data, err := result.AsBytes()
	if err != nil {
		return nil
	}

	var item T
	if err := json.Unmarshal(data, &item); err != nil {
		return nil
	}

	return &item
}

func (c *CacheUtil[T]) Invalidate(key string) {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()

	fullKey := c.prefix + key
	c.client.Do(ctx, c.client.B().Del().Key(fullKey).Build())
}
