package org.springblade.common.cache.support;

import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.Tuple;
import redis.clients.jedis.params.SetParams;

import javax.annotation.Resource;
import java.util.*;


@Service
public class JedisService {

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

    @Resource
    @Setter
    private ShardedJedisPool jedisPool;

    @Value("${service.cache.prefix:}")
    private String servicePrefix;

    private static HessianCodecFactory hessianCodecFactory = new HessianCodecFactory();

    /**
     * 缓存180秒
     */
    public static final int REF_SECONDS = 180;

    @SuppressWarnings("deprecation")
    public void returnResource(ShardedJedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

    /**
     * 得到Cache名
     *
     * @param type
     * @param key
     * @return
     */
    private String getCacheNameString(String type, Object key) {
        StringBuilder cacheName = new StringBuilder();
        if (!StringUtils.isEmpty(servicePrefix)) {
            cacheName.append(servicePrefix).append(":");
        }
        cacheName.append(type);
        if (key != null && key.toString().length() > 0) {
            cacheName.append(":").append(key);
        }
        return cacheName.toString();
    }

    /**
     * 得到Cache名
     *
     * @param type
     * @param key
     * @return
     */
    private byte[] getCacheName(String type, Object key) {
        return this.getCacheNameString(type, key).getBytes();
    }

    /**
     * 序列化
     *
     * @param value
     * @return
     */
    private byte[] getSerializable(Object value) {
        return hessianCodecFactory.serialize(value);
    }

    /**
     * 反序列化
     *
     * @param value
     * @return
     */
    private Object getDeserialization(byte[] value) {
        if (value == null) {
            return null;
        }
        try {
            return hessianCodecFactory.deserialize(value);
        } catch (Exception e) {
            logger.error("Deserialize throw exception: ", e);
            return null;
        }
    }

    /**
     * 添加一个指定的值到缓存中.
     *
     * @param key
     * @param value
     * @return
     */
    public void putInCache(String type, Object key, Object value) {
        putInCache(type, key, value, REF_SECONDS);
    }

    /**
     * 无时限缓存
     *
     * @param type
     * @param key
     * @param value
     */
    public void putNoTimeInCache(String type, Object key, Object value) {
        if (value != null) {
            putInCache(type, key, value, -1);
        }
    }

    /**
     * 写入Cache
     *
     * @param type
     * @param key
     * @param value
     * @param seconds
     */
    public void putInCache(String type, Object key, Object value, int seconds) {
        if (value != null) {
            byte[] cacheName = getCacheName(type, key);
            byte[] v = this.getSerializable(value);
            if (v != null) {
                ShardedJedis jedis = null;
                try {
                    jedis = jedisPool.getResource();
                    if (seconds < 1) {
                        jedis.set(cacheName, v);
                    } else {
                        jedis.setex(cacheName, seconds, v);
                    }
                } catch (Exception e) {
                    logger.error("Put cache for " + getCacheNameString(type, key) + " throw exception: ", e);
                } finally {
                    this.returnResource(jedis);
                }
            }
        }
    }

    /**
     * 写入Cache NX EX
     *
     * @param type
     * @param key
     * @param value
     * @param seconds
     */
    public boolean putInCacheIfAbsent(String type, Object key, Object value, int seconds) {
        if (value != null) {
            byte[] cacheName = getCacheName(type, key);
            byte[] v = this.getSerializable(value);
            if (v != null) {
                ShardedJedis jedis = null;
                try {
                    jedis = jedisPool.getResource();
                    SetParams setParams = new SetParams();
                    String result;
                    if (seconds < 1) {
                        result = jedis.set(cacheName, v, setParams.nx().ex(Integer.MAX_VALUE));
                    } else {
                        result = jedis.set(cacheName, v, setParams.nx().ex(seconds));
                    }
                    return "OK".equalsIgnoreCase(result);
                } catch (Exception e) {
                    logger.error("Put cache if absent for " + getCacheNameString(type, key) + " throw exception: ", e);
                } finally {
                    this.returnResource(jedis);
                }
            }
        }
        return false;
    }

    /**
     * 删除缓存
     *
     * @param type
     * @param key
     */
    public void deleteCache(String type, Object key) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            jedis.del(getCacheName(type, key));
        } catch (Exception e) {
            logger.error("Delete cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
    }

    /**
     * 根据指定的关键字获取对象.
     *
     * @param key
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T getFromCache(Class<T> clazz, String type, Object key) {
        return (T) getFromCache(type, key);
    }

    /**
     * 读取一个列表
     *
     * @param <T>
     * @param clazz
     * @param type
     * @param key
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> List<T> getListFromCache(Class<T> clazz, String type, Object key) {
        return (List<T>) getFromCache(type, key);
    }

    public Object getFromCache(String type, Object key) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            byte[] v = jedis.get(getCacheName(type, key));
            return this.getDeserialization(v);
        } catch (Exception e) {
            logger.error("Get cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 存储计数
     *
     * @param type
     * @param key
     * @param value
     */
    public long putCounterCache(String type, Object key, long value, int ttl) {
        long ret = 0;
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            ret = jedis.setnx(getCacheNameString(type, key), String.valueOf(value));
            jedis.expire(getCacheNameString(type, key), ttl);
        } catch (Exception e) {
            logger.error("Put counter cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return ret;
    }

    /**
     * 配置过期
     *
     * @param type
     * @param key
     * @param ttl  缓存秒数
     */
    public void expire(String type, Object key, int ttl) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            jedis.expire(getCacheNameString(type, key), ttl);
        } catch (Exception e) {
            logger.error("Expire cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
    }

    /**
     * 配置过期时间
     *
     * @param type
     * @param key
     * @param expiredDate 过期日期,单位为秒数
     */
    public void expireAt(String type, Object key, long expiredDate) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            jedis.expireAt(getCacheNameString(type, key), expiredDate);
        } catch (Exception e) {
            logger.error("Expire cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
    }

    /**
     * 获取key的过期时间
     *
     * @param type
     * @param key
     * @return 返回过期剩余时间（秒）
     */
    public long getExpire(String type, Object key) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.ttl(getCacheNameString(type, key));
        } catch (Exception e) {
            logger.error("Get cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return 0;
    }

    /**
     * 改变存储计数
     *
     * @param type
     * @param key
     * @param value
     */
    public long incrCounterCache(String type, Object key, long value) {
        long ret = 0;
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if (value >= 0) {
                ret = jedis.incrBy(getCacheName(type, key), Math.abs(value));
            } else {
                ret = jedis.decrBy(getCacheName(type, key), Math.abs(value));
            }
        } catch (Exception e) {
            logger.error("Incr counter cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return ret;
    }

    /**
     * 得到计数器数值
     *
     * @param type
     * @param key
     * @return
     */
    public Long getCounter(String type, Object key) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            byte[] v = jedis.get(getCacheName(type, key));
            if (v != null) {
                return NumberUtil.parseLong(new String(v));
            } else {
                return null;
            }
        } catch (Exception e) {
            logger.error("Get Counter for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 判断是否存在此Key
     *
     * @param type
     * @param key
     * @return
     */
    public boolean exists(String type, Object key) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            boolean ret = jedis.exists(getCacheName(type, key));
            return ret;
        } catch (Exception e) {
            logger.error("Check key for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return false;
    }

    public ShardedJedisPool getJedisPool() {
        return jedisPool;
    }

    /**
     * 根据指定的关键字获取字符串，不做反序列化
     *
     * @param type
     * @param key
     * @return
     */
    public String getStringFromCache(String type, Object key) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.get(getCacheNameString(type, key));
        } catch (Exception e) {
            logger.error("Get cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 根据指定的关键字获取字符串，不做反序列化
     *
     * @param key
     * @return
     */
    public String getFromCacheByKey(Object key) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.get(String.valueOf(key));
        } catch (Exception e) {
            logger.error("Get cache for " + key + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * hsetnx 将哈希表 key 中的域 field 的值设置为 value ，当且仅当域 field 不存在
     * 根据指定关键字操作域
     * DESC 将哈希表 key 中的域 field 的值设置为 value ，当且仅当域 field 不存在。
     * 若域 field 已经存在，该操作无效。
     * 如果 key 不存在，一个新哈希表被创建并执行 HSETNX 命令。
     * RETURN 设置成功，返回 1 。
     * 如果给定域已经存在且没有操作被执行，返回 0 。
     *
     * @param type
     * @param key
     * @return
     */
    public Long putFieldInCache(String type, String field, String key, String value) {
        Long result = 0L;
        if (value != null) {
            ShardedJedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                result = jedis.hsetnx(getCacheNameString(type, key), field, value);
                return result;
            } catch (Exception e) {
                logger.error("Put cache for " + getCacheNameString(type, key) + " throw exception: ", e);
            } finally {
                this.returnResource(jedis);
            }
        }
        return result;
    }

    /**
     * 获取Hash中Key中所有域的值
     *
     * @param type
     * @param key
     * @return
     */
    public List<String> getHashAllValues(String type, String key) {
        List<String> getValues = new ArrayList<>(12);
        if (key != null) {
            ShardedJedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                getValues = jedis.hvals(getCacheNameString(type, key));
                return getValues;
            } catch (Exception e) {
                logger.error("Get cache for " + getCacheNameString(type, key) + " throw exception: ", e);
            } finally {
                this.returnResource(jedis);
            }
        }
        return getValues;
    }

    /**
     * 获取HASH的值
     *
     * @param type
     * @param key
     * @return
     */
    public Map<String, String> getHashFromCache(String type, String key) {
        Map<String, String> result = new HashMap<>();
        String cacheName = getCacheNameString(type, key);
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            result = jedis.hgetAll(cacheName);
            return result;
        } catch (Exception e) {
            logger.error("Get cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return result;
    }

    /**
     * 获取HASH的值
     *
     * @param key
     * @return
     */
    public Map<String, String> getHashFromCacheByKey(String key) {
        Map<String, String> result = new HashMap<>();
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            result = jedis.hgetAll(key);
            return result;
        } catch (Exception e) {
            logger.error("Get cache for " + key + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return result;
    }

    /**
     * 操作Key
     *
     * @param type
     * @param keys
     * @return
     */
    public Set<String> getByKeys(String type, String keys) {
        ShardedJedis jedis = null;
        Set<String> result = new HashSet<>();
        try {
            jedis = jedisPool.getResource();
            result = jedis.hkeys(getCacheNameString(type, keys));
            return result;
        } catch (Exception e) {
            logger.error("Get cache for " + getCacheNameString(type, keys) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return result;
    }

    /**
     * 同时将多个 field-value (域-值)对设置到哈希表 key 中
     * 会覆盖哈希表中已存在的域
     *
     * @param key
     * @param fields
     */
    public String hmSet(String type, String key, Map<String, String> fields, Integer seconds) {
        ShardedJedis jedis = null;
        String cacheName = getCacheNameString(type, key);
        try {
            jedis = jedisPool.getResource();
            String result = jedis.hmset(cacheName, fields);
            if (seconds != null && seconds > 0) {
                jedis.expire(cacheName, seconds);
            }
            return result;
        } catch (Exception e) {
            logger.error("Hash set cache for " + cacheName + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 获取哈希表key中，一个或多个给定域的值
     *
     * @param key
     * @param fields
     * @return
     */
    public List<String> hmGet(String type, String key, String... fields) {
        ShardedJedis jedis = null;
        String cacheName = getCacheNameString(type, key);
        try {
            jedis = jedisPool.getResource();
            return jedis.hmget(cacheName, fields);
        } catch (Exception e) {
            logger.error("Hash get cache for " + cacheName + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 删除哈希表 key 中的一个或多个指定域，不存在的域将被忽略
     *
     * @param type
     * @param key
     * @param fields
     * @return
     */
    public Long hDel(String type, String key, String... fields) {
        ShardedJedis jedis = null;
        String cacheName = getCacheNameString(type, key);
        try {
            jedis = jedisPool.getResource();
            return jedis.hdel(cacheName, fields);
        } catch (Exception e) {
            logger.error("Hash delete for " + cacheName + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return 0L;
    }

    /**
     * 获取set集合的成员列表
     *
     * @param type
     * @param key
     * @return
     */
    public Set<String> listSetMembers(String type, String key) {
        ShardedJedis jedis = null;
        String cacheName = getCacheNameString(type, key);
        try {
            jedis = jedisPool.getResource();
            return jedis.smembers(cacheName);
        } catch (Exception e) {
            logger.error("List set members for " + cacheName + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 添加set中成员，支持多个
     *
     * @param type
     * @param key
     * @param members
     * @return
     */
    public Long addSetMembers(String type, String key, String... members) {
        ShardedJedis jedis = null;
        String cacheName = getCacheNameString(type, key);
        try {
            jedis = jedisPool.getResource();
            return jedis.sadd(cacheName, members);
        } catch (Exception e) {
            logger.error("Add set members for " + cacheName + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 移除set中的成员，支持多个
     *
     * @param type
     * @param key
     * @param members
     * @return
     */
    public Long delSetMembers(String type, String key, String... members) {
        ShardedJedis jedis = null;
        String cacheName = getCacheNameString(type, key);
        try {
            jedis = jedisPool.getResource();
            return jedis.srem(cacheName, members);
        } catch (Exception e) {
            logger.error("Del set members for " + cacheName + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 移除并返回列表 key 的头元素
     *
     * @param type
     * @param key
     * @return
     */
    public String lPop(String type, String key) {
        ShardedJedis jedis = null;
        String cacheName = getCacheNameString(type, key);
        try {
            jedis = jedisPool.getResource();
            return jedis.lpop(cacheName);
        } catch (Exception e) {
            logger.error("Lpop cache for " + cacheName + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 移除并返回列表 key 的尾元素
     *
     * @param type
     * @param key
     * @return
     */
    public String rPop(String type, String key) {
        ShardedJedis jedis = null;
        String cacheName = getCacheNameString(type, key);
        try {
            jedis = jedisPool.getResource();
            return jedis.rpop(cacheName);
        } catch (Exception e) {
            logger.error("Rpop cache for " + cacheName + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 将一个或多个值 value 插入到列表 key 的表头
     *
     * @param type
     * @param key
     * @param values
     * @return
     */
    public Long lPush(String type, String key, String... values) {
        ShardedJedis jedis = null;
        String cacheName = getCacheNameString(type, key);
        try {
            jedis = jedisPool.getResource();
            return jedis.lpush(cacheName, values);
        } catch (Exception e) {
            logger.error("LPush cache for " + cacheName + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return 0L;
    }

    /**
     * 将一个或多个值 value 插入到列表 key 的表尾(最右边)
     *
     * @param type
     * @param key
     * @param values
     * @return
     */
    public Long rPush(String type, String key, String... values) {
        ShardedJedis jedis = null;
        String cacheName = getCacheNameString(type, key);
        try {
            jedis = jedisPool.getResource();
            return jedis.rpush(cacheName, values);
        } catch (Exception e) {
            logger.error("R push cache for " + cacheName + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return 0L;
    }

    /**
     * 添加zset中成员
     *
     * @param type
     * @param key
     * @param score
     * @return
     */
    public Long zadd(String type, String key, double score) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.zadd(type, score, key);
        } catch (Exception e) {
            logger.error("Zadd cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 增减zset元素的score
     *
     * @param type
     * @param key
     * @param score
     * @return
     */
    public Double zincrby(String type, String key, double score) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.zincrby(type, score, key);
        } catch (Exception e) {
            logger.error("Z increase cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 获取项在zset中倒序的索引
     *
     * @param type
     * @param key
     * @return
     */
    public Long zrevrank(String type, String key) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.zrevrank(type, key);
        } catch (Exception e) {
            logger.error("Z rev rank cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 通过key返回zset中value的排名
     * 下标从小到大排序
     *
     * @param type
     * @param key
     * @return
     */
    public Long zrank(String type, String key) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.zrank(type, key);
        } catch (Exception e) {
            logger.error("Z rank cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 获取项在zset中的分数值
     *
     * @param type
     * @param key
     * @return
     */
    public Double zscore(String type, String key) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.zscore(type, key);
        } catch (Exception e) {
            logger.error("Z score cache for " + getCacheNameString(type, key) + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 通过key获取数据列表
     *
     * @param key
     * @return
     */
    public Set<Tuple> zrevrangeWithScores(String key, long start, long end) {
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.zrevrangeWithScores(key, start, end);
        } catch (Exception e) {
            logger.error("Get cache for " + key + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return null;
    }

    /**
     * 操作符合条件的keys
     *
     * @param pattern
     * @return
     */
    public Set<String> getKeys(String pattern) {
        ShardedJedis jedis = null;
        Set<String> result = new HashSet<>();
        try {
            jedis = jedisPool.getResource();
            for (Jedis shard : jedis.getAllShards()) {
                result.addAll(shard.keys(pattern));
            }
            return result;
        } catch (Exception e) {
            logger.error("Get key for pattern" + pattern + " throw exception: ", e);
        } finally {
            this.returnResource(jedis);
        }
        return result;
    }
}
