package redis

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
	"go.signoz.io/signoz/pkg/query-service/cache/status"
	"go.uber.org/zap"
)

type cache struct {
	client *redis.Client
	opts   *Options
}

// New creates a new cache
func New(opts *Options) *cache {
	if opts == nil {
		opts = defaultOptions()
	}
	return &cache{opts: opts}
}

// WithClient creates a new cache with the given client
func WithClient(client *redis.Client) *cache {
	return &cache{client: client}
}

// Connect connects to the redis server
func (c *cache) Connect() error {
	c.client = redis.NewClient(&redis.Options{
		Addr:     fmt.Sprintf("%s:%d", c.opts.Host, c.opts.Port),
		Password: c.opts.Password,
		DB:       c.opts.DB,
	})
	return nil
}

// Store stores the data in the cache
func (c *cache) Store(cacheKey string, data []byte, ttl time.Duration) error {
	return c.client.Set(context.Background(), cacheKey, data, ttl).Err()
}

// Retrieve retrieves the data from the cache
func (c *cache) Retrieve(cacheKey string, allowExpired bool) ([]byte, status.RetrieveStatus, error) {
	data, err := c.client.Get(context.Background(), cacheKey).Bytes()
	if err != nil {
		if err == redis.Nil {
			return nil, status.RetrieveStatusKeyMiss, nil
		}
		return nil, status.RetrieveStatusError, err
	}
	return data, status.RetrieveStatusHit, nil
}

// SetTTL sets the TTL for the cache entry
func (c *cache) SetTTL(cacheKey string, ttl time.Duration) {
	err := c.client.Expire(context.Background(), cacheKey, ttl).Err()
	if err != nil {
		zap.L().Error("error setting TTL for cache key", zap.String("cacheKey", cacheKey), zap.Duration("ttl", ttl), zap.Error(err))
	}
}

// Remove removes the cache entry
func (c *cache) Remove(cacheKey string) {
	err := c.client.Del(context.Background(), cacheKey).Err()
	if err != nil {
		zap.L().Error("error deleting cache key", zap.String("cacheKey", cacheKey), zap.Error(err))
	}
}

// BulkRemove removes the cache entries
func (c *cache) BulkRemove(cacheKeys []string) {
	for _, cacheKey := range cacheKeys {
		c.Remove(cacheKey)
	}
}

// Close closes the connection to the redis server
func (c *cache) Close() error {
	return c.client.Close()
}

// Ping pings the redis server
func (c *cache) Ping() error {
	return c.client.Ping(context.Background()).Err()
}

// GetClient returns the redis client
func (c *cache) GetClient() *redis.Client {
	return c.client
}

// GetOptions returns the options
func (c *cache) GetOptions() *Options {
	return c.opts
}

// GetTTL returns the TTL for the cache entry
func (c *cache) GetTTL(cacheKey string) time.Duration {
	ttl, err := c.client.TTL(context.Background(), cacheKey).Result()
	if err != nil {
		zap.L().Error("error getting TTL for cache key", zap.String("cacheKey", cacheKey), zap.Error(err))
	}
	return ttl
}

// GetKeys returns the keys matching the pattern
func (c *cache) GetKeys(pattern string) ([]string, error) {
	return c.client.Keys(context.Background(), pattern).Result()
}

// GetKeysWithTTL returns the keys matching the pattern with their TTL
func (c *cache) GetKeysWithTTL(pattern string) (map[string]time.Duration, error) {
	keys, err := c.GetKeys(pattern)
	if err != nil {
		return nil, err
	}
	result := make(map[string]time.Duration)
	for _, key := range keys {
		result[key] = c.GetTTL(key)
	}
	return result, nil
}
