package redis

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

	"github.com/go-redis/redis/v8"
	"gitlab.com/uafrica/go-utils/errors"
	"gitlab.com/uafrica/go-utils/logger"
)

type IRedis interface {
	Del(key string) error
	SetJSON(key string, value interface{}) error
	SetJSONIndefinitely(key string, value interface{}) error
	SetJSONForDur(key string, value interface{}, dur time.Duration) error
	GetJSON(key string, valueType reflect.Type) (value interface{}, ok bool)
	SetString(key string, value string) error
	SetStringIndefinitely(key string, value string) error
	SetStringForDur(key string, value string, dur time.Duration) error
	GetString(key string) (value string, ok bool)
}

type redisWithContext struct {
	context.Context
	client *redis.Client
}

func New(ctx context.Context) (IRedis, error) {
	if globalClient == nil {
		var err error
		if globalClient, err = connect(); err != nil {
			return redisWithContext{Context: ctx}, errors.Wrapf(err, "cannot connect to REDIS")
		}
	}
	return redisWithContext{
		Context: ctx,
		client:  globalClient,
	}, nil
}

func (r redisWithContext) Del(key string) error {
	if r.client == nil {
		return errors.Errorf("REDIS disabled: cannot del key(%s)", key)
	}
	_, err := r.client.Del(r.Context, key).Result()
	if err != nil {
		return errors.Wrapf(err, "failed to del key(%s)", key)
	}
	logger.Debugf("REDIS.Del(%s)", key)
	return nil
}

//set JSON value for 24h
func (r redisWithContext) SetJSON(key string, value interface{}) error {
	return r.SetJSONForDur(key, value, 24*time.Hour)
}

func (r redisWithContext) SetJSONIndefinitely(key string, value interface{}) error {
	return r.SetJSONForDur(key, value, 0)
}

func (r redisWithContext) SetJSONForDur(key string, value interface{}, dur time.Duration) error {
	if r.client == nil {
		return errors.Errorf("REDIS disabled: cannot set JSON key(%s) = (%T)%v", key, value, value)
	}
	jsonBytes, err := json.Marshal(value)
	if err != nil {
		return errors.Wrapf(err, "failed to JSON encode key(%s) = (%T)", key, value)
	}
	if _, err = r.client.Set(r.Context, key, string(jsonBytes), dur).Result(); err != nil {
		return errors.Wrapf(err, "failed to set JSON key(%s)", key)
	}
	logger.Debugf("REDIS.SetJSON(%s)=%s (%T) (exp: %v)", key, string(jsonBytes), value, dur)
	return nil
}

//return:
//	nil,nil if key is not defined
//	nil,err if failed to get/determine if it exists, or failed to decode
//	<value>,nil if found and decoded
func (r redisWithContext) GetJSON(key string, valueType reflect.Type) (value interface{}, ok bool) {
	if r.client == nil {
		return nil, false
	}
	jsonValue, err := r.client.Get(r.Context, key).Result()
	if err != nil {
		return nil, false
	}
	newValuePtr := reflect.New(valueType)
	if err := json.Unmarshal([]byte(jsonValue), newValuePtr.Interface()); err != nil {
		return nil, false
	}
	return newValuePtr.Elem().Interface(), true
}

func (r redisWithContext) SetString(key string, value string) error {
	return r.SetStringForDur(key, value, 24*time.Hour)
}

func (r redisWithContext) SetStringIndefinitely(key string, value string) error {
	return r.SetStringForDur(key, value, 0)
}

func (r redisWithContext) SetStringForDur(key string, value string, dur time.Duration) error {
	if r.client == nil {
		return errors.Errorf("REDIS disabled: cannot set key(%s) = (%T)%v", key, value, value)
	}
	if _, err := r.client.Set(r.Context, key, value, dur).Result(); err != nil {
		return errors.Wrapf(err, "failed to set key(%s)", key)
	}
	logger.Debugf("REDIS.SetString(%s)=%s (exp: %v)", key, value, dur)
	return nil
}

func (r redisWithContext) GetString(key string) (string, bool) {
	if r.client == nil {
		return "", false
	}
	value, err := r.client.Get(r.Context, key).Result()
	if err != nil { /* Actual error */
		if err != redis.Nil { /* other than Key does not exist */
			logger.Errorf("Error fetching redis key(%s): %+v", key, err)
		}
		return "", false
	}
	return value, true
}

//global connection to REDIS used in all context
var globalClient *redis.Client

func connect() (*redis.Client, error) {
	host := os.Getenv("REDIS_HOST")
	if host == "false" {
		return nil, errors.Errorf("REDIS_HOST=false")
	}

	port := os.Getenv("REDIS_PORT")
	if os.Getenv("DEBUGGING") == "true" {
		host = "host.docker.internal"
		if os.Getenv("LOCAL") == "true" {
			host = "localhost"
		}
		env := os.Getenv("ENVIRONMENT")
		switch env {
		case "dev":
			port = "6380"
		case "stage":
			port = "6381"
		case "prod":
			port = "6383"
		}
	}
	logger.Debugf("Using REDIS(%s:%s)", host, port)
	globalClient = redis.NewClient(&redis.Options{
		Addr:     host + ":" + port,
		Password: "", // no password set
		DB:       0,  // use default DB
	})
	return globalClient, nil
} //connect()