package myredis

import (
	"context"
	"fmt"
	"git.makemake.in/kzkzzzz/mycommon/myconf"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
	"log"
	"sync"
	"time"
)

const (
	DefaultInstance = "redis"
)

type Client struct {
	*redis.Client
	redisOpt    *redis.Options
	disablePing bool
}

//type Conf struct {
//	Addr            string
//	Password        string
//	Db              int
//	PoolSize        int    // 连接池数量 如果不够用会继续新建连接 可以用 MaxActiveConns 限制
//	MinIdleConn     int    // 最小空闲连接数 预热可能用到
//	MaxIdleConn     int    // 最大空闲连接数 0不限制
//	MaxActiveConn   int    // 0 不限制, 如果不限制, 超出pool size可以继续新建立连接, 但使用完之后不会放回连接池
//	PoolTimeout     string // 等待连接池超时时间 Default is ReadTimeout + 1 second.
//	DialTimeout     string // 拨号超时时间
//	ReadTimeout     string // 读取超时
//	WriteTimeout    string // 写入超时
//	MaxRetries      int    // 重试次数
//	ConnMaxIdleTime string // 连接空闲时间
//}

var (
	defaultClient *redis.Client
	instanceMap   = &sync.Map{}
)

func GetClient(name ...string) *Client {
	var instanceName string
	if len(name) > 0 {
		instanceName = name[0]
	} else {
		instanceName = DefaultInstance
	}

	v, ok := instanceMap.Load(instanceName)
	if !ok {
		panic(fmt.Errorf("redis instance [%s] not init", instanceName))
	}

	return v.(*Client)
}

// InitClient 初始化全局默认client
func InitClient(config *myconf.Config, opts ...Opt) {
	client, err := NewClient(DefaultInstance, config, opts...)
	if err != nil {
		panic(err)
	}
	instanceMap.Store(DefaultInstance, client)
}

// InitClientInstance 初始化全局的client
func InitClientInstance(instanceName string, config *myconf.Config, opts ...Opt) {
	client, err := NewClient(instanceName, config, opts...)
	if err != nil {
		panic(err)
	}
	instanceMap.Store(instanceName, client)
}

func NewClient(instanceName string, config *myconf.Config, opts ...Opt) (*Client, error) {
	cf := &Conf{}
	err := config.UnmarshalKey(instanceName, &cf)
	if err != nil {
		return nil, err
	}
	client, err := NewClientFromConf(cf, opts...)
	if err != nil {
		return nil, err
	}

	instanceMap.Store(instanceMap, client)
	return client, nil
}

type Conf struct {
	Addr            string
	Password        string
	Db              int
	PoolSize        int    // 连接池数量 如果不够用会继续新建连接 可以用 MaxActiveConns 限制
	MinIdleConn     int    // 最小空闲连接数 预热可能用到
	MaxIdleConn     int    // 最大空闲连接数 0不限制
	MaxActiveConn   int    // 0 不限制, 如果不限制, 超出pool size可以继续新建立连接, 但使用完之后不会放回连接池
	DialTimeout     string // 拨号超时时间, 时间单位格式 10ms, 60s, 15m, 2h...
	ReadTimeout     string // 读取超时
	WriteTimeout    string // 写入超时
	ConnMaxIdleTime string // 连接空闲时间
	PoolTimeout     string // 等待连接池超时时间 Default is ReadTimeout + 1 second.
	MaxRetries      int    // 重试次数
}

func NewClientFromConf(cf *Conf, opts ...Opt) (*Client, error) {
	c := &Client{
		redisOpt: DefaultRedisOpt(),
	}

	for _, opt := range opts {
		opt(c)
	}

	err := c.parseConfToRedisOpt(cf)
	if err != nil {
		return nil, err
	}

	client := redis.NewClient(c.redisOpt)

	if c.disablePing == false {
		_, err := client.Ping(context.Background()).Result()
		if err != nil {
			return nil, fmt.Errorf("redis ping err: %s", err)
		}
	}

	c.Client = client

	instanceMap.Store(uuid.New().String(), c)

	log.Printf("connect redis success [addr:%s - db:%d]", cf.Addr, cf.Db)

	return c, nil
}

func DefaultRedisOpt() *redis.Options {
	return &redis.Options{
		Addr:                  "",
		Password:              "",
		DB:                    0,
		MaxRetries:            3,                         // 重试次数
		DialTimeout:           time.Millisecond * 500,    // 拨号超时时间
		ReadTimeout:           time.Second,               // 读取超时
		WriteTimeout:          time.Second * 3,           // 写入超时
		PoolSize:              1024,                      // 连接池数量 如果不够用会继续新建连接 可以用 MaxActiveConns 限制
		PoolTimeout:           time.Second + time.Second, // 等待连接池超时时间 Default is ReadTimeout + 1 second.
		MinIdleConns:          0,                         // 最小空闲连接数 预热可能用到
		MaxIdleConns:          0,                         // 最大空闲连接数 0不限制
		MaxActiveConns:        0,                         // 0 不限制, 如果不限制, 超出pool size可以继续新建立连接, 但使用完之后不会放回连接池
		ConnMaxIdleTime:       time.Minute * 10,          // 连接空闲时间
		ContextTimeoutEnabled: true,                      // context控制超时用到
	}
}

func (c *Client) parseTime(v string) (time.Duration, error) {
	if v == "" {
		return 0, nil
	}

	d, err := time.ParseDuration(v)
	if err != nil {
		return 0, fmt.Errorf("parse time %s err: %s", v, err)
	}

	return d, nil
}

func (c *Client) parseConfToRedisOpt(cf *Conf) error {
	c.redisOpt.Addr = cf.Addr
	c.redisOpt.Password = cf.Password
	c.redisOpt.DB = cf.Db

	if v := cf.PoolSize; v > 0 {
		c.redisOpt.PoolSize = v
	}

	if v := cf.MinIdleConn; v > 0 {
		c.redisOpt.MinIdleConns = v
	}

	if v := cf.MaxIdleConn; v > 0 {
		c.redisOpt.MaxIdleConns = v
	}

	if v := cf.MaxActiveConn; v > 0 {
		c.redisOpt.MaxActiveConns = v
	}

	if v := cf.MaxRetries; v > 0 {
		c.redisOpt.MaxRetries = v
	}

	if v, err := c.parseTime(cf.DialTimeout); err != nil {
		return err
	} else if v > 0 {
		c.redisOpt.DialTimeout = v
	}

	if v, err := c.parseTime(cf.ReadTimeout); err != nil {
		return err
	} else if v > 0 {
		c.redisOpt.ReadTimeout = v
	}

	if v, err := c.parseTime(cf.WriteTimeout); err != nil {
		return err
	} else if v > 0 {
		c.redisOpt.WriteTimeout = v
	}

	if v, err := c.parseTime(cf.ConnMaxIdleTime); err != nil {
		return err
	} else if v > 0 {
		c.redisOpt.ConnMaxIdleTime = v
	}

	if v, err := c.parseTime(cf.PoolTimeout); err != nil {
		return err
	} else if v > 0 {
		c.redisOpt.PoolTimeout = v
	}

	return nil
}

const luaSetOnce = `if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end`

// SetOnce 设置一次并设置过期时间, key不存在则设置成功返回1, key已存在返回0
func (c *Client) SetOnce(ctx context.Context, key, value string, t time.Duration) (int, error) {
	if t < time.Second {
		return 0, fmt.Errorf("time must >= 1s")
	}
	return c.Client.Eval(ctx, luaSetOnce, []string{key}, value, t).Int()
}

func CloseAllClient() {
	instanceMap.Range(func(k, v any) bool {
		v.(*Client).Close()
		return true
	})
}
