package cache

import (
	"time"

	"github.com/go-redis/redis"
	"github.com/pkg/errors"
	"github.com/vmihailenco/msgpack"
)

var ErrNotFound = errors.New("not found")

// RedisCache implements caching layer for feeds using Redis
//
// Inside docker can be connected as:
//  docker exec -it redis redis-cli
// View available keys:
//  127.0.0.1:6379> keys *
//
type RedisCache struct {
	client *redis.Client
}

func NewRedisCache(redisURL string) (RedisCache, error) {
	opts, err := redis.ParseURL(redisURL)
	if err != nil {
		return RedisCache{}, err
	}

	client := redis.NewClient(opts)
	if err := client.Ping().Err(); err != nil {
		return RedisCache{}, err
	}

	return RedisCache{client: client}, nil
}

func (c RedisCache) Set(key, value string, ttl time.Duration) error {
	return c.client.Set(key, value, ttl).Err()
}

func (c RedisCache) Get(key string) (string, error) {
	val, err := c.client.Get(key).Result()
	if err == redis.Nil {
		return "", ErrNotFound
	} else {
		return val, err
	}
}

func (c RedisCache) SaveItem(key string, item interface{}, exp time.Duration) error {
	data, err := msgpack.Marshal(item)
	if err != nil {
		return err
	}

	return c.client.Set(key, data, exp).Err()
}

func (c RedisCache) GetItem(key string, item interface{}) error {
	data, err := c.client.Get(key).Bytes()
	if err == redis.Nil {
		return ErrNotFound
	} else if err != nil {
		return err
	}

	if err := msgpack.Unmarshal(data, item); err != nil {
		return err
	}

	return nil
}

func (c RedisCache) SetMap(key string, fields map[string]interface{}, exp time.Duration) error {
	if err := c.client.HMSet(key, fields).Err(); err != nil {
		return err
	}

	if err := c.client.TTL(key).Err(); err != nil {
		return err
	}

	return nil
}

func (c RedisCache) GetMap(key string, fields ...string) (map[string]string, error) {
	result, err := c.client.HMGet(key, fields...).Result()
	if err == redis.Nil {
		return nil, ErrNotFound
	} else if err != nil {
		return nil, err
	}

	data := map[string]string{}
	for idx, key := range fields {
		if result[idx] == nil {
			continue
		}

		data[key] = result[idx].(string)
	}

	if len(data) == 0 {
		return nil, ErrNotFound
	}

	return data, nil
}

func (c RedisCache) Invalidate(key... string) error {
	return c.client.Del(key...).Err()
}

func (c RedisCache) Close() error {
	return c.client.Close()
}