package bird

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

	"github.com/go-redis/redis/v8"
)

type RedisCache struct {
	client    *redis.Client
	keyPrefix string
}

func NewRedisCache(config CacheConfig) (*RedisCache, error) {

	client := redis.NewClient(&redis.Options{
		Addr:     config.RedisServer,
		Password: config.RedisPassword,
		DB:       config.RedisDb,
	})

	ctx := context.Background()
	_, err := client.Ping(ctx).Result()
	if err != nil {
		return nil, err
	}

	cache := &RedisCache{
		client: client,
	}

	return cache, nil
}

// Get retrievs a birdwatcher `Parsed` result from
// the redis cache.
func (self *RedisCache) Get(key string) (Parsed, error) {
	ctx := context.Background()
	key = self.keyPrefix + key //"B" + IPVersion + "_" + key
	data, err := self.client.Get(ctx, key).Result()
	if err != nil {
		return NilParse, err
	}

	parsed := Parsed{}
	err = json.Unmarshal([]byte(data), &parsed)

	ttl, err := parseCacheTTL(parsed["ttl"])
	if err != nil {
		return NilParse, fmt.Errorf("invalid TTL value for key: %s", key)
	}
	// Deal with the inband TTL if present
	if !ttl.Equal(time.Time{}) && ttl.Before(time.Now()) {
		return NilParse, err // TTL expired
	}

	return parsed, err // cache hit
}

// Set adds a birdwatcher `Parsed` result
// to the redis cache.
func (self *RedisCache) Set(key string, parsed Parsed, ttl int) error {
	switch {
	case ttl == 0:
		return nil // do not cache

	case ttl > 0:
		key = self.keyPrefix + key //TODO "B" + IPVersion + "_" + key
		payload, err := json.Marshal(parsed)
		if err != nil {
			return err
		}

		ctx := context.Background()
		_, err = self.client.Set(
			ctx, key, payload, time.Duration(ttl)*time.Minute).Result()
		return err

	default: // ttl negative - invalid
		return fmt.Errorf("negative TTL value for key: %s", key)
	}
}

func (self *RedisCache) Expire() int {
	log.Printf("Cannot expire entries in RedisCache backend, redis does this automatically")
	return 0
}

// Helperfunction to decode the cache ttl stored
// in the cache - which will most likely just be
// RFC3339 timestamp.
func parseCacheTTL(cacheTTL interface{}) (time.Time, error) {
	if cacheTTL == nil {
		// We preseve the nil value as a zero value
		return time.Time{}, nil
	}

	switch cacheTTL.(type) {
	case string:
		ttl, err := time.Parse(time.RFC3339, cacheTTL.(string))
		if err != nil {
			return time.Time{}, err
		}
		return ttl, nil
	case time.Time:
		return cacheTTL.(time.Time), nil
	}
	return time.Time{}, nil
}
