package cachehandler

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

	redisCache "github.com/go-redis/cache/v8"
	"github.com/go-redis/redis/v8"
	"github.com/patrickmn/go-cache"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/utils/panicIf"
	"github.com/pingcap/log"
	"github.com/spf13/viper"
	"go.uber.org/zap"
)

type Cache interface {
	Get(string, interface{}, ...interface{}) bool
	Set(string, interface{}, ...interface{})
	Delete(string, ...interface{})
}

type CacheImpl struct {
	c *cache.Cache
}

const DefaultTimeout = 60 * time.Minute

var global Cache = nil

func init() {
	InitWithLocal()
}

func Get() Cache {
	return global
}

func Set(cache Cache) {
	global = cache
}

func Init() {
	redis := redis.NewClient(&redis.Options{
		Addr: viper.GetString(config.RedisHost),
	})
	cache := redisCache.New(
		&redisCache.Options{
			Redis: redis,
		},
	)
	global = &RedisCacheImpl{
		c: cache,
	}
}

func InitWithLocal() {
	global = &CacheImpl{
		c: cache.New(DefaultTimeout, 2*DefaultTimeout),
	}
}

const CACHE_KEY_DELIM = "#"

func buildCacheKey(funcName string, args ...interface{}) string {
	cache_key := funcName
	for _, arg := range args {
		cache_key = cache_key + CACHE_KEY_DELIM + fmt.Sprintf("%v", arg)
	}
	return cache_key
}

func (cache *CacheImpl) Get(funcName string, value interface{}, args ...interface{}) bool {
	if !viper.GetBool(config.EnableCache) {
		return false
	}
	key := buildCacheKey(funcName, args...)
	cacheValue, found := cache.c.Get(key)
	if found && value != nil {
		jsonMustUnmarshal(cacheValue.([]byte), value)
	}
	log.Info("get cache", zap.String("key", key), zap.Bool("found", found))
	return found
}

func jsonMustMarshal(obj interface{}) []byte {
	res, err := json.Marshal(obj)
	panicIf.Error(err)
	return res
}

func jsonMustUnmarshal(data []byte, obj interface{}) {
	err := json.Unmarshal(data, obj)
	panicIf.Error(err)
}

func (cache *CacheImpl) Set(funcName string, value interface{}, args ...interface{}) {
	if !viper.GetBool(config.EnableCache) {
		return
	}
	key := buildCacheKey(funcName, args...)
	log.Info("set cache", zap.String("key", key))
	cache.c.SetDefault(key, jsonMustMarshal(value))
}

func (cache *CacheImpl) Delete(funcName string, args ...interface{}) {
	if !viper.GetBool(config.EnableCache) {
		return
	}
	key := buildCacheKey(funcName, args...)
	cache.c.Delete(key)
}

type RedisCacheImpl struct {
	c *redisCache.Cache
}

func (cache *RedisCacheImpl) Get(funcName string, value interface{}, args ...interface{}) bool {
	if !viper.GetBool(config.EnableCache) {
		return false
	}
	key := buildCacheKey(funcName, args...)
	err := cache.c.Get(context.Background(), key, value)
	found := err == nil
	log.Info("get cache", zap.String("key", key), zap.Bool("found", found))
	return found
}

func (cache *RedisCacheImpl) Set(funcName string, value interface{}, args ...interface{}) {
	if !viper.GetBool(config.EnableCache) {
		return
	}
	key := buildCacheKey(funcName, args...)
	log.Info("set cache", zap.String("key", key))
	_ = cache.c.Set(&redisCache.Item{
		Key:   key,
		Value: value,
		TTL:   DefaultTimeout,
	})
}

func (cache *RedisCacheImpl) Delete(funcName string, args ...interface{}) {
	if !viper.GetBool(config.EnableCache) {
		return
	}
	key := buildCacheKey(funcName, args...)
	_ = cache.c.Delete(context.Background(), key)
}
