package com.heyu.api.data.utils;

import com.google.gson.Gson;
import com.heyu.api.data.constants.RedisConstans;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;


import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类
 */
@Component
public class RedisUtils {

    protected static Logger logger = LoggerFactory.getLogger(RedisUtils.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private ValueOperations<String, String> valueOperations;
    @Autowired
    private HashOperations<String, String, Object> hashOperations;
    @Autowired
    private ListOperations<String, Object> listOperations;
    @Autowired
    private SetOperations<String, Object> setOperations;
    @Autowired
    private ZSetOperations<String, Object> zSetOperations;

    @Autowired
    @Qualifier("incrRedisTemplate")
    private RedisTemplate incrRedisTemplate;
    /**
     * 默认过期时长，单位：秒
     */
    public final static long DEFAULT_EXPIRE = 7 * 60 * 60 * 24;
    /**
     * 默认过期时长，单位：秒
     */
    public final static long thirty_minute = 30 * 60;


    /**
     * 默认过期时长，单位：秒
     */
    public final static long one_hours = 1 * 60 * 60;

    /**
     * 不设置过期时长
     */
    public final static long NOT_EXPIRE = -1;
    private final static Gson GSON = new Gson();

    public static boolean BIZ_CACHE_SWITCH = true;//业务缓存开关，true：打开（即使用缓存）  false：关闭（即不使用缓存）

    public void set(String key, Object value, long expire) {
        try {
            valueOperations.set(key, toJson(value));
            if (expire != NOT_EXPIRE) {
                redisTemplate.expire(key, expire, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            logger.error("set redis error", e);
        }


    }

    public void set(String key, Object value) {
        set(key, value, DEFAULT_EXPIRE);
    }

    /**
     * 设置缓存，key已存在返回falseH
     *
     * @param key
     * @param value
     * @return 创建日期：2017年8月10日
     * 修改说明：
     * @author wutao
     */
    public boolean setIfAbsent(String key, String value, int timeout) {
        return setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
    }

    public boolean setIfAbsent(String key, String value, int timeout, TimeUnit unit) {
        boolean flag = valueOperations.setIfAbsent(key, value);
        if (flag && timeout > 0) {
            redisTemplate.expire(key, timeout, unit);
        }
        return flag;
    }

    public <T> T get(String key, Class<T> clazz, long expire) {
        String value = valueOperations.get(key);
        redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        return value == null ? null : fromJson(value, clazz);
    }

    public <T> T get(String key, Class<T> clazz) {
        try {
            if(com.heyu.api.data.utils.StringUtils.isBlank(key)){
                return null;
            }
            String value = valueOperations.get(key);
            return value == null ? null : fromJson(value, clazz);
        } catch (Exception e) {
            logger.error("get redis error", e);
        }
        return null;
    }

    public String get(String key, long expire) {
        String value = valueOperations.get(key);
        redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        return value;
    }

    public String get(String key) {
        try {
            return valueOperations.get(key);
        } catch (Exception e) {
        }
        return null;
    }


    public String getAndSet(String key, String value, long expire) {
        value = valueOperations.getAndSet(key, value);
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value;
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 获取缓存对象
     *
     * @param key 缓存key
     * @return
     */
    public Object getObject(final String key) {
        if (!BIZ_CACHE_SWITCH || StringUtils.isBlank(key)) {
            return null;
        }
        try {
            return redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    byte[] getResult = connection.get(redisTemplate.getStringSerializer().serialize(key));
                    if (getResult == null || getResult.length == 0) {
                        return null;
                    }
                    return SerializeUtil.unserialize(getResult);
                }
            });
        } catch (Exception e) {
            logger.error("getObject error", e);
            return null;
        }
    }

    /**
     * 保存到缓存，不设过期时间
     *
     * @param key     缓存key
     * @param seriObj 缓存数据
     */
    public void saveObjectWithOutExpire(final String key, final Serializable seriObj) {
        if (!BIZ_CACHE_SWITCH || StringUtils.isBlank(key) || seriObj == null) {
            return;
        }
        try {
            redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    connection.set(redisTemplate.getStringSerializer().serialize(key), SerializeUtil.serialize(seriObj));
                    return null;
                }
            });
        } catch (Exception e) {
            logger.error("saveObjectWithOutExpire", e);
        }
    }

    /**
     * 删除缓存
     *
     * @param key 需要删除缓存的id
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void delCache(final String key) {
        if (!BIZ_CACHE_SWITCH) {
            return;
        }
        try {
            redisTemplate.execute(new RedisCallback() {
                @Override
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.del(redisTemplate.getStringSerializer().serialize(key));
                }
            });
        } catch (Exception e) {
            logger.error("delCache error", e);

        }
    }

    /**
     * 锁住某个key值30S，需要解锁时删除即可
     *
     * @param key
     * @return
     */
    public boolean lock30Second(final String key) {
        final String value = "xx";
        try {
            Boolean flag = set(key, value);
            if (flag) {
                redisTemplate.execute(new RedisCallback<Object>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.expire(redisTemplate.getStringSerializer().serialize(key), 30L);
                    }
                });
            }
            return flag;
        } catch (Exception e) {
            logger.error("getLock error", e);
            return false;
        }
    }

    private Boolean set(String key, String value) {
        return (Boolean) redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.setNX(redisTemplate.getStringSerializer().serialize(key), value.getBytes());
            }
        });
    }

    /**
     * Object转成JSON数据
     */
    private String toJson(Object object) {
        if (object instanceof Integer || object instanceof Long || object instanceof Float ||
                object instanceof Double || object instanceof Boolean || object instanceof String) {
            return String.valueOf(object);
        }
        return GSON.toJson(object);
    }

    /**
     * JSON数据，转成Object
     */
    private <T> T fromJson(String json, Class<T> clazz) {
        return GSON.fromJson(json, clazz);
    }

    public int countByPrefix(String prefix) {
        try {
            Set<String> keys = redisTemplate.keys(prefix + "*");
            return keys.size();
        } catch (Exception e) {
            logger.error("redis delete prefix key error", e);
        }
        return 0;
    }

    /**
     * 设置值自增（无键则原子设置初始值）
     *
     * @param key
     * @param value
     * @param expireSec
     * @param dxalSourceQuota
     * @return
     */
    public Double incrValue(String key, Double value, long expireSec, Double dxalSourceQuota) {
        Double total = value;
        // 为了防止多次保存初始值 导致初始值错误
        Boolean flag = incrRedisTemplate.opsForValue().setIfAbsent(key, value.toString());
        if (flag) {
            logger.info("incrValue key = {}, value = {}", key, value);
            incrRedisTemplate.expire(key, expireSec, TimeUnit.SECONDS);

        } else {
            synchronized (RedisUtils.class) {
                Double oldValue = Double.parseDouble(String.valueOf(incrRedisTemplate.opsForValue().get(key)));
                total = oldValue + value;
                if (total <= dxalSourceQuota) {
                    incrRedisTemplate.opsForValue().increment(key, value);
                }
            }
        }
        return total;
    }


    public void incrValue(String key, Double value) {
        incrRedisTemplate.opsForValue().increment(key, value);
    }

    /**
     * 设置值自减
     *
     * @param key
     * @param value
     * @return
     */
    public void subValue(String key, Double value) {
        try {
            Double total = value;
            // 为了防止多次保存初始值 导致初始值错误
            Object oldValue = incrRedisTemplate.opsForValue().get(key);
            if (oldValue == null) {
                return;
            }
            incrRedisTemplate.opsForValue().increment(key, value * -1);
        } catch (Exception e) {
            logger.error("bizCache subValue error", e);
        }
    }


    /**
     * 保存到缓存，并设定过期时间
     *
     * @param key     缓存key
     * @param seriObj 缓存数据
     * @param expire  过期时间
     */
    public void saveObject(final String key, final Serializable seriObj, final long expire) {
        if (StringUtils.isBlank(key) || seriObj == null) {
            return;
        }
        try {
            redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    connection.set(redisTemplate.getStringSerializer().serialize(key), SerializeUtil.serialize(seriObj));
                    connection.expire(redisTemplate.getStringSerializer().serialize(key), expire);
                    return null;
                }
            });
        } catch (Exception e) {
            logger.error("saveObject", e);
        }
    }


    /**
     * 锁住某个key值30S，需要解锁时删除即可
     *
     * @param key
     * @param value
     * @return
     */
    public boolean getLock30Second(final String key, final String value) {
        try {
            Boolean flag = (Boolean) redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.setNX(redisTemplate.getStringSerializer().serialize(key), value.getBytes());
                }
            });
            if (flag) {
                redisTemplate.execute(new RedisCallback<Object>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.expire(redisTemplate.getStringSerializer().serialize(key), RedisConstans.SECOND_OF_THREE);
                    }
                });
            }
            return flag;
        } catch (Exception e) {
            logger.error("getLock error", e);
            return false;
        }
    }


    /**
     * 重试 获取锁
     *
     * @param key
     * @param lockTime ，获得锁以后的锁定时间
     * @return
     */
    public boolean lockTryTimes(final String key, final Long tryTimes, final Long lockTime) {
        final String value = "xx";
        boolean flag = false;
        try {
            // 请求锁超时时间，毫秒
            long timeout = tryTimes * 1000;
            // 系统当前时间，毫秒
            long nowTime = System.currentTimeMillis();
            while ((System.currentTimeMillis() - nowTime) < timeout) {
                if (set(key, value)) {
                    flag = true;
                    break;
                }
                // 每次请求等待一段时间
                seleep(10, 50000);
            }
            if (flag) {
                redisTemplate.execute(new RedisCallback<Object>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.expire(redisTemplate.getStringSerializer().serialize(key), lockTime);
                    }
                });
            }
        } catch (Exception e) {
            logger.error("getLock error", e);
        }
        return flag;
    }


    final Random random = new Random();


    private void seleep(long millis, int nanos) {
        try {
            Thread.sleep(millis, random.nextInt(nanos));
        } catch (InterruptedException e) {
            logger.info("获取分布式锁休眠被中断：", e);
        }
    }

    public boolean lock60Second(final String key) {
        final String value = "xx";
        try {
            Boolean flag = set(key, value);
            if (flag) {
                redisTemplate.execute(new RedisCallback<Object>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.expire(redisTemplate.getStringSerializer().serialize(key), RedisConstans.SECOND_OF_SIXTY);
                    }
                });
            }
            return flag;
        } catch (Exception e) {
            logger.error("getLock error", e);
            return false;
        }
    }


    /**
     * 删除缓存
     *
     * @param key 需要删除缓存的id
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void unLock(final String key) {
        //如果为空
        if (com.heyu.api.data.utils.StringUtils.isBlank(key)) {
            return;
        }
        try {
            redisTemplate.execute(new RedisCallback() {
                @Override
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.del(redisTemplate.getStringSerializer().serialize(key));
                }
            });
        } catch (Exception e) {
            logger.error("delCache error", e);
        }
    }


    public Long incr(String key, long liveTime) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        long increment = entityIdCounter.getAndIncrement();
        if (increment == 0 && liveTime > 0) {
            //初始设置过期时间
            entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
        }
        return increment;
    }




    /**
     * 尝试获取第三方访问权限，包括 QPS 检查和分布式锁。
     *
     * @param api         请求接口
     * @param MaxRequests 请求限制数
     * @return 是否成功获取访问权限
     */
    public boolean tryGetAccess(String api, int MaxRequests, int timeWindow) {
        // 设置 QPS 限制和锁的键
        String qpsKey = "qps:limit:" + api;
        Date currentDate = new Date();

        Date nextDate = DateUtils.addSeconds(currentDate, timeWindow);
        // 秒
        int liveTime = DateUtils.betweenMillisSecond(currentDate, nextDate);
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(qpsKey, Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        long increment = entityIdCounter.getAndIncrement();
        // 只有第一次时才会 设置过期时间
        if (increment == 0 && liveTime > 0) {
            //初始设置过期时间
            entityIdCounter.expire(liveTime, TimeUnit.MILLISECONDS);
        }
        // 如果 QPS 超过限制或未能获得锁，则返回 false
        if (increment >= MaxRequests) {
            return false;
        }
        return true;
    }

}
