
package com.tmerclub.cloud.common.cache.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.tmerclub.cloud.common.cache.constant.CacheNames;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.util.SpringContextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author tmerclub
 * @date 2020/7/11
 */
public class RedisUtil {

    private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);

    @SuppressWarnings("unchecked")
    private static final RedisTemplate<String, Object> REDIS_TEMPLATE = SpringContextUtils.getBean("redisTemplate",
            RedisTemplate.class);

    public static final StringRedisTemplate STRING_REDIS_TEMPLATE = SpringContextUtils.getBean("stringRedisTemplate",
            StringRedisTemplate.class);

    public static final String CAD_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    // =============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     */
    public static void expire(String key, long time) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        try {
            if (time > 0) {
                REDIS_TEMPLATE.expire(key, time, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            logger.error("Set expire error: {}", e.getMessage());
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回-1代表为永久有效 失效时间为0，说明该主键未设置失效时间（失效时间默认为-1）
     */
    public static Long getExpire(String key) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        return REDIS_TEMPLATE.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false 不存在
     */
    public static Boolean hasKey(String key) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        try {
            return REDIS_TEMPLATE.hasKey(key);
        } catch (Exception e) {
            logger.error("Error getting hasKey: {}", e.getMessage());
            return Boolean.FALSE;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    public static void del(String... key) {
        if (key != null && key.length > 0) {
            for (String s : key) {
                if (s.contains(StrUtil.SPACE)) {
                    throw new LuckException(ResponseEnum.EXCEPTION);
                }
            }

            if (key.length == 1) {
                REDIS_TEMPLATE.delete(key[0]);
            } else {
                REDIS_TEMPLATE.delete(Arrays.asList(key));
            }
        }
    }

    /**
     * 删除缓存
     * @param keys 可以传一个值 或多个
     */
    public static void del(List<String> keys) {
        if (CollUtil.isEmpty(keys)) {
            return;
        }
        REDIS_TEMPLATE.delete(keys);
    }

    // ============================String=============================

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    @SuppressWarnings("unchecked")
    public static <T> T get(String key) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        return (T) REDIS_TEMPLATE.opsForValue().get(key);
    }
    /**
     * 批量缓存获取
     * @param keys 键
     * @return 值
     */
    @SuppressWarnings("unchecked")
    public static <T> List<T> list(Collection<String> keys) {
        for (String key : keys) {
            if (key.contains(StrUtil.SPACE)) {
                throw new LuckException(ResponseEnum.EXCEPTION);
            }
        }
        return (List<T>) STRING_REDIS_TEMPLATE.opsForValue().multiGet(keys);
    }
    /**
     * 批量缓存获取
     * @param keyName 键名
     * @param ids
     * @return 值
     */
    @SuppressWarnings("unchecked")
    public static <T> Map<Long, T> mapByIds(String keyName, List<Long> ids, Class<T> tClass) {
        ids = CollUtil.distinct(ids);
        List<String> keys = new ArrayList<>(ids.size());
        for (Long id : ids) {
            keys.add(keyName + CacheNames.UNION + id);
        }
        Map<Long, T> map = new HashMap<>(Constant.INITIAL_CAPACITY);
        for (Object o : STRING_REDIS_TEMPLATE.opsForValue().multiGet(keys)) {
            Long id = ids.remove(0);
            if (o != null) {
                T t = JSONObject.parseObject(o.toString(), tClass);
                map.put(id, t);
            }
        }
        return map;
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     */
    public static void set(String key, Object value, long time) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        try {
            if (time > 0) {
                REDIS_TEMPLATE.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                REDIS_TEMPLATE.opsForValue().set(key, value);
            }
        } catch (Exception e) {
            logger.error("Redis opsForValue error: {}", e.getMessage());
        }
    }

    /**
     * list缓存放入并设置时间
     *
     * @param key  键
     * @param list 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void setRightPushAll(String key, List list, long time) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        try {
            if (time > 0) {
                REDIS_TEMPLATE.opsForList().rightPushAll(key, list);
                expire(key, time);
            } else {
                REDIS_TEMPLATE.opsForList().rightPushAll(key, list);
            }
        } catch (Exception e) {
            logger.error("Redis opsForList error: {}", e.getMessage());
        }
    }

    /**
     * 获取list缓存的size
     * @param key 键
     * @return true成功 false 失败
     */
    public static Long getListSize(String key) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        try {
            return REDIS_TEMPLATE.opsForList().size(key);
        } catch (Exception e) {
            logger.error("Redis opsForList error: {}", e.getMessage());
            return 0L;
        }
    }

    /**
     * 根据key 获取list指定范围的内容
     * @param key 键 不能为null
     * @param start
     * @param end
     * @return 时间(秒) 返回-1代表为永久有效 失效时间为0，说明该主键未设置失效时间（失效时间默认为-1）
     */
    @SuppressWarnings("rawtypes")
    public static List getListRange(String key, Long start, Long end) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        return REDIS_TEMPLATE.opsForList().range(key, start, end);
    }

    /**
     * map放入缓存,HMSET操作
     * @param key 键 不能为null
     * @param map
     */
    public static void hmSet(StringRedisTemplate redisTemplate, String key, Map<Object, Object> map) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        if(Objects.nonNull(redisTemplate)){
            redisTemplate.opsForHash().putAll(key, map);
        }else {
            STRING_REDIS_TEMPLATE.opsForHash().putAll(key, map);
        }
    }

    /**
     * field放入缓存,HSET操作
     * @param key 键 不能为null
     * @param hashKey
     * @param value
     */
    public static void hSet(StringRedisTemplate redisTemplate, String key, String hashKey, String value) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        if(Objects.nonNull(redisTemplate)){
            redisTemplate.opsForHash().put(key, hashKey, value);
        }else {
            STRING_REDIS_TEMPLATE.opsForHash().put(key, hashKey, value);
        }
    }

    /**
     * 删除map缓存中的键值对
     * @param key 键 不能为null
     * @param hashKeys
     */
    public static void hDelete(StringRedisTemplate redisTemplate, String key, Object... hashKeys) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        if(Objects.nonNull(redisTemplate)){
            redisTemplate.opsForHash().delete(key, hashKeys);
        }else {
            STRING_REDIS_TEMPLATE.opsForHash().delete(key, hashKeys);
        }
    }

    /**
     * map缓存获取,HGET操作
     * @param key 键 不能为null
     * @param hashKey
     */
    public static Object hGet(StringRedisTemplate redisTemplate, String key, String hashKey) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        if(Objects.nonNull(redisTemplate)){
            return redisTemplate.opsForHash().get(key, hashKey);
        }else {
            return STRING_REDIS_TEMPLATE.opsForHash().get(key, hashKey);
        }
    }


    /**
     * 获取field有多少个
     * @param key 键 不能为null
     * @return 数量
     */
    public static Long hSize(StringRedisTemplate redisTemplate, String key) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        if(Objects.nonNull(redisTemplate)){
            return redisTemplate.opsForHash().size(key);
        }else {
            return STRING_REDIS_TEMPLATE.opsForHash().size(key);
        }
    }

    /**
     * 获取所有的field和value,
     * @param key 键 不能为null
     * @return 所有field
     */
    public static Map<Object, Object> entries(StringRedisTemplate redisTemplate, String key) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        if(Objects.nonNull(redisTemplate)){
            return redisTemplate.opsForHash().entries(key);
        }else {
            return STRING_REDIS_TEMPLATE.opsForHash().entries(key);
        }
    }

    /**
     * 获取模糊匹配的的所有key
     * @return 所有field
     */
    public static List<String> hScan(StringRedisTemplate redisTemplate,String cachePrefix) {
        if(Objects.nonNull(redisTemplate)){
            Cursor<String> cursor = null;
            List<String> list = new ArrayList<>();
            try {
                ScanOptions options = ScanOptions.scanOptions()
                        .count(1000)
                        .match(cachePrefix + "*")
                        .build();
                RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
                cursor = redisTemplate.executeWithStickyConnection(redisConnection ->
                        new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
                if(Objects.isNull(cursor)){
                    return list;
                }
                while (cursor.hasNext()) {
                    list.add(cursor.next());
                }
                return list;
            } finally {
                if(Objects.nonNull(cursor)) {
                    cursor.close();
                }
            }
        }
        return new ArrayList<>();
    }


    /**
     * 递增 此时value值必须为int类型 否则报错
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return 自增后的值
     */
    public static Long incr(String key, long delta) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return STRING_REDIS_TEMPLATE.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public static void decr(String key, long delta) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        if (delta < 0) {
            throw new RuntimeException("递减因子必须小于0");
        }
        STRING_REDIS_TEMPLATE.opsForValue().increment(key, -delta);
    }

    public static void setLongValue(String key, Long value, long time) {
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        try {
            if (time > 0) {
                STRING_REDIS_TEMPLATE.opsForValue().set(key, String.valueOf(value), time, TimeUnit.SECONDS);
            } else {
                STRING_REDIS_TEMPLATE.opsForValue().set(key, String.valueOf(value));
            }
        } catch (Exception e) {
            logger.error("setLongValue() error: {}", e.getMessage());
        }
    }

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public static Long getLongValue(String key) {
        if (key == null) {
            return null;
        }
        if (key.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        String result = STRING_REDIS_TEMPLATE.opsForValue().get(key);
        if (result == null) {
            return null;
        }
        return Long.valueOf(result);
    }

    /**
     * 批量删除缓存
     * @param keys
     */
    public static void deleteBatch(List<String> keys) {
        if (CollUtil.isEmpty(keys)) {
            return;
        }
        for (String key : keys) {
            if (key.contains(StrUtil.SPACE)) {
                throw new LuckException(ResponseEnum.EXCEPTION);
            }
        }
        REDIS_TEMPLATE.delete(keys);
    }

    /**
     * 批量删除缓存
     * @param cacheName 缓存名
     * @param cacheKeys 缓存key
     */
    public static void deleteBatch(String cacheName, List<?> cacheKeys) {
        if (StrUtil.isBlank(cacheName) || CollUtil.isEmpty(cacheKeys)) {
            return;
        }
        List<String> strCacheKeys = cacheKeys.stream().map(Object::toString).collect(Collectors.toList());
        List<String> keys = new ArrayList<>(1);
        for (String cacheKey : strCacheKeys) {
            String key = cacheName + CacheNames.UNION + cacheKey;
            keys.add(key);
            if (key.contains(StrUtil.SPACE)) {
                throw new LuckException(ResponseEnum.EXCEPTION);
            }
        }
        REDIS_TEMPLATE.delete(keys);
    }

    /**
     * 比较和删除标记，原子性
     * @return 是否成功
     */
    public static boolean cad(String key, String value) {

        if (key.contains(StrUtil.SPACE) || value.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }


        //通过lure脚本原子验证令牌和删除令牌
        Long result = eval(CAD_SCRIPT, key, value, Long.class);

        return !Objects.equals(result, 0L);
    }

    public static Set<String> keys(String pattern) {
        throw new RuntimeException("请不要使用keys");
    }

    /**
     * 比较和删除标记，原子性
     * @return 是否成功
     */
    public static <T> T eval(String script, String key, String arg, Class<T> resultType) {
        if (key.contains(StrUtil.SPACE) || arg.contains(StrUtil.SPACE)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        return STRING_REDIS_TEMPLATE.execute(new DefaultRedisScript<>(script, resultType),
                Collections.singletonList(key),
                arg);
    }
}
