package com.njcn.redis.utils;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;


/**
 * @author hongawen
 * @version 1.0.0
 * @date 2021年04月30日 09:38
 */
@Component
@RequiredArgsConstructor
public class RedisUtil {

    private final RedisTemplate<String, Object> redisTemplate;

    /**
     * 指定key的失效时间
     * 秒级别的过期时间
     */
    public void expire(String key, long time) {
        redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }

    /**
     * 根据key获取过期时间
     */
    public long getExpire(String key) {
        Long expireTime = redisTemplate.getExpire(key, TimeUnit.SECONDS);
        return Objects.isNull(expireTime) ? 0 : expireTime;
    }

    /**
     * 根据key获取过期时间(切库，切完之后自动换为原来库)
     */
    public long getExpire(Integer dbIndex,String key) {
       return getExpire(dbIndex,key,true);
    }

    /**
     * 根据key获取过期时间(切库)true:切回原库 false:不切回原库
     */
    public long getExpire(Integer dbIndex,String key,Boolean fly) {
        Integer index = setDbIndex(dbIndex);
        Long expireTime = redisTemplate.getExpire(key, TimeUnit.SECONDS);
        if(fly){
            setDbIndex(index);
        }
        return Objects.isNull(expireTime) ? 0 : expireTime;
    }
    /**
     * 判断key是否存在
     */
    public boolean hasKey(String key) {
        Boolean hasKeyFlag = redisTemplate.hasKey(key);
        if (Objects.isNull(hasKeyFlag)) {
            return false;
        }
        return hasKeyFlag;
    }

    /**
     * 删除某个Key
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 批量删除keys
     */
    public void deleteKeys(String... keys) {
        redisTemplate.delete(Arrays.asList(keys));
    }

    /**
     * 指定字符模糊匹配，批量删除keys
     */
    public void deleteKeysByString(String str) {
        Set<String> keys = redisTemplate.keys(str.concat("*"));
        // 删除所有匹配的key
        if (keys != null && !keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }


    /**
     * 获取key对应的字符数据
     */
    public String getStringByKey(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }

    /**
     * 根据key和库索引获取内容字(切库之后，之后查询都是切库数据)
     *
     * @param dbIndex 指定库索引
     * @param key     key值
     */
    public String getStringByKey(Integer dbIndex,String key) {
       return getStringByKey(dbIndex,key,true);
    }
    /**
     * 根据key和库索引获取内容字(切库之后，之后查询都是切库数据)
     *
     * @param dbIndex 指定库索引
     * @param key     key值
     * @param fly     是否切回原库
     */
    public String getStringByKey(Integer dbIndex,String key,Boolean fly) {
        Integer index = setDbIndex(dbIndex);
        String s = (String) redisTemplate.opsForValue().get(key);
        if(fly){
            setDbIndex(index);
        }
        return s;
    }

    /**
     * 获取key对应对象数据
     */
    public Object getObjectByKey(String key) {
        return redisTemplate.opsForValue().get(key);
    }


    /**
     * 保存数据
     *
     * @param key   键
     * @param value 值
     */
    public void saveByKey(String key, Object value) {
        redisTemplate.boundValueOps(key).set(value);

    }

    /**
     * 保存数据，指定生命周期（秒）
     *
     * @param key        键
     * @param value      值
     * @param expireTime 生命时间
     */
    public void saveByKeyWithExpire(String key, Object value, Long expireTime) {
        if (expireTime <= 0) {
            saveByKey(key, value);
        } else {
            redisTemplate.boundValueOps(key).set(value, expireTime, TimeUnit.SECONDS);
        }
    }
    /**
     * 保存数据，指定生命周期（秒）
     *
     * @param dbIndex 指定库索引
     * @param key        键
     * @param value      值
     * @param expireTime 生命时间
     * @param expireTime 生命时间
     */
    public void saveByKeyWithExpire(Integer dbIndex,String key, Object value, Integer expireTime) {
        saveByKeyWithExpire(dbIndex,key,value,expireTime,true);
    }
    /**
     * 保存数据，指定生命周期（秒）
     *
     * @param dbIndex 指定库索引
     * @param key        键
     * @param value      值
     * @param fly        是否切回原库
     */
    public void saveByKeyWithExpire(Integer dbIndex,String key, Object value, Integer expireTime,Boolean fly) {
        Integer index = setDbIndex(dbIndex);
        if (expireTime <= 0) {
            saveByKey(key, value);
        } else {
            redisTemplate.boundValueOps(key).set(value, expireTime, TimeUnit.SECONDS);
        }
        if(fly){
            setDbIndex(index);
        }
    }

    /**
     * 顺序的递增和递减
     *
     * @param value 增减根据数值的正负来判断
     */
    public void increment(String key, Long value) {
        redisTemplate.boundValueOps(key).increment(value);
    }


    /**
     * 添加一个Map集合
     */
    public void saveMapValue(String key, Map<String, ?> map, long expireTime) {
        redisTemplate.boundHashOps(key).putAll(map);
        if (expireTime > 0) {
            expire(key, expireTime);
        }
    }

    /**
     * 获取map中所有的keys
     */
    public Set<?> getMapKeys(String key) {
        return redisTemplate.boundHashOps(key).keys();
    }

    /**
     * 获取map中所有的values
     */
    public List<?> getMapValues(String key) {
        return redisTemplate.boundHashOps(key).values();
    }

    /**
     * 根据map中某个key获取对应的value
     */
    public Object getMapValueByMapKey(String redisKey, String mapKey) {
        return redisTemplate.boundHashOps(redisKey).get(mapKey);
    }

    /**
     * 根据map中的某个key删除对应的value
     */
    public void deleteMapValueByMapKey(String redisKey, String mapKey) {
        redisTemplate.boundHashOps(redisKey).delete(mapKey);
    }

    /**
     * 判断map中是否有指定的key
     */
    public boolean hasMapKey(String redisKey, String mapKey) {
        Boolean hasKey = redisTemplate.boundHashOps(redisKey).hasKey(mapKey);
        if (Objects.isNull(hasKey)) {
            return false;
        }
        return hasKey;
    }

    /**
     * 右存放List
     */
    public void saveRightListByKey(String key, List<?> values, long expireTime) {
        redisTemplate.boundListOps(key).rightPushAll(values);
        if (expireTime > 0) {
            expire(key, expireTime);
        }
    }

    /**
     * 左存放List
     */
    public void saveLeftListByKey(String key, List<?> values, long expireTime) {
        redisTemplate.boundListOps(key).leftPushAll(values);
        if (expireTime > 0) {
            expire(key, expireTime);
        }
    }


    /**
     * 获取List某范围的值
     *
     * @param start 起始索引
     * @param end   截止索引
     */
    public List<?> getListRangeValues(String key, long start, long end) {
        long size = getListSize(key);
        if ((start < 0 && end < 0) || (start > end)) {
            start = 0;
            end = size;
        } else if (end > size) {
            end = size;
        } else if (start < 0) {
            start = 0;
        }
        return redisTemplate.boundListOps(key).range(start, end);
    }

    /**
     * 获取List所有数据
     */
    public List<?> getListAllValues(String key) {
        long size = getListSize(key);
        return redisTemplate.boundListOps(key).range(0, size);
    }

    /**
     * 根据指定索引获取指定value
     */
    public Object getListValueByIndex(String key, int index) {
        return redisTemplate.boundListOps(key).index(index);
    }

    /**
     * 获取list长度
     */
    public Long getListSize(String key) {
        return Objects.isNull(redisTemplate.boundListOps(key).size()) ? 0 : redisTemplate.boundListOps(key).size();
    }

    /**
     * 根据索引修改某个值
     */
    public void updateListValueByIndex(String key, int index, Object newObj) {
        redisTemplate.boundListOps(key).set(index, newObj);
    }

    /**
     * 根据某个key模糊查询,并取出value
     * @param key
     * @return
     */
    public List<?> getLikeListAllValues(String key) {
        List<Object> info = new ArrayList<>();
        // 使用 SCAN 替代 keys，解决 Tair 禁用 KEYS 命令的问题
        Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<String> keySet = new HashSet<>();
            ScanOptions options = ScanOptions.scanOptions()
                    .match(key + "*")
                    .count(1000)
                    .build();

            try (Cursor<byte[]> cursor = connection.scan(options)) {
                while (cursor.hasNext()) {
                    keySet.add(new String(cursor.next()));
                }
            } catch (Exception e) {
                throw new RuntimeException("Redis SCAN 模糊查询失败", e);
            }
            return keySet;
        });
        for (String s : keys) {
            info.add(redisTemplate.opsForValue().get(s));
        }
        return info;
    }

    /**
     * 根据key模糊查询,并取出所有符合条件的key集合
     * @return
     */

    public Set<String> scanKeysByPattern(String pattern, int count) {
        Set<String> keys = new HashSet<>();
        ScanOptions options = ScanOptions.scanOptions()
                .match(pattern)
                .count(count)
                .build();

        try (Cursor<byte[]> cursor = redisTemplate.getConnectionFactory()
                .getConnection()
                .scan(options)) {
            while (cursor.hasNext()) {
                keys.add(new String(cursor.next()));
            }
        } catch (Exception e) {
            throw new RuntimeException("扫描Redis keys失败", e);
        }
        return keys;
    }

    /**
     * 数据切库
     * @param dbIndex
     * @return
     */
    private Integer setDbIndex(Integer dbIndex) {
        if (dbIndex == null || dbIndex > 15 || dbIndex < 0) {
            dbIndex = 0;
        }
        LettuceConnectionFactory jedisConnectionFactory = (LettuceConnectionFactory) redisTemplate
                .getConnectionFactory();
        int database = jedisConnectionFactory.getDatabase();
        jedisConnectionFactory.setDatabase(dbIndex);
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        //需要刷新才能生效
        jedisConnectionFactory.afterPropertiesSet();
        // 重置连接
        jedisConnectionFactory.resetConnection();
        return database;
    }


    /**
     * 获取value为字符串的
     * @param key
     * @return
     */
    public String getRawValue(String key) {
        return redisTemplate.execute((RedisCallback<String>) connection -> {
            byte[] data = connection.get(key.getBytes());
            return data == null ? null : new String(data, StandardCharsets.UTF_8);
        });
    }

}
