package cache

import (
	"encoding/json"
	"fmt"

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

type RedisConfig struct {
	Addrs        []string
	Password     string
	DB           json.Number
	PoolSize     json.Number
	MinIdleConns json.Number
	MaxRetries   json.Number
	IsCluster    string
}

type RedisCache struct {
	isCluster bool
	client    *redis.Client
	cluster   *redis.ClusterClient
}

func (p *RedisCache) Init(config RedisConfig) {
	if len(config.Addrs) == 0 {
		panic("Redis's addrs is empty.")
	}

	poolSize, _ := config.PoolSize.Int64()
	minIdleConns, _ := config.MinIdleConns.Int64()
	maxRetries, _ := config.MaxRetries.Int64()

	// redis is cluster?
	p.isCluster = (config.IsCluster == "true")
	if p.isCluster {
		p.cluster = redis.NewClusterClient(&redis.ClusterOptions{
			Addrs:        config.Addrs,
			Password:     config.Password,
			PoolSize:     int(poolSize),
			MinIdleConns: int(minIdleConns),
			MaxRetries:   int(maxRetries),
		})

		// check if redis server is ok.
		if _, err := p.cluster.Ping().Result(); err != nil {
			panic(err)
		}
	} else {
		db, _ := config.DB.Int64()

		p.client = redis.NewClient(&redis.Options{
			Addr:         config.Addrs[0],
			Password:     config.Password,
			DB:           int(db),
			PoolSize:     int(poolSize),
			MinIdleConns: int(minIdleConns),
			MaxRetries:   int(maxRetries),
		})

		// check if redis server is ok.
		if _, err := p.client.Ping().Result(); err != nil {
			panic(err)
		}
	}
}

func (p *RedisCache) commandReturnStringSlice(args ...interface{}) (res []string, err error) {
	if p.isCluster {
		res, err = strings(p.cluster.Do(args...).Result())
		if err != nil {
			if res == nil || len(res) <= 0 {
				return nil, redis.Nil
			}
		}

		return
	}

	res, err = strings(p.client.Do(args...).Result())
	if err != nil {
		if res == nil || len(res) <= 0 {
			return nil, redis.Nil
		}
	}

	return
}

type Error string

func (err Error) Error() string { return string(err) }

func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error {
	if err != nil {
		return err
	}
	switch reply := reply.(type) {
	case []interface{}:
		makeSlice(len(reply))
		for i := range reply {
			if reply[i] == nil {
				continue
			}
			if err := assign(i, reply[i]); err != nil {
				return err
			}
		}
		return nil
	case nil:
		return redis.Nil
	case Error:
		return reply
	}

	return fmt.Errorf("cache: unexpected type for %s, got type %T", name, reply)
}

func strings(reply interface{}, err error) ([]string, error) {
	var result []string
	err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {
		switch v := v.(type) {
		case string:
			result[i] = v
			return nil
		case []byte:
			result[i] = string(v)
			return nil
		default:
			return fmt.Errorf("cache: unexpected element type for Strings, got type %T", v)
		}
	})

	return result, err
}
