package com.ssj.framework.core.security.manager.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ssj.framework.core.security.manager.TokenManager;
import com.ssj.framework.core.util.CodecUtil;
import com.ssj.framework.core.util.StringUtil;
import com.ssj.framework.weixin.util.WeixinUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;

import java.util.*;

/**
 *
 * 类名称：RedisTokenManager<br>
 * 类描述：基于 Redis 的令牌管理器<br>
 * @version v1.0
 *
 */
@Service
public class RedisTokenManager implements TokenManager {

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

    /**
     * Redis中key是userId  value 是token
     */
    private static final String REDIS_TOKEN_TOKEN_PREFIX = "TOKEN_TOKEN_";

    /**
     * Redis中key是token  value 是      userId
     */
    private static final String REDIS_TOKEN_USERID_PREFIX = "TOKEN_USERID_";


    /**
     * Redis中key是token value 是       openId
     */
    private static final String REDIS_TOKEN_OPENID_PREFIX = "TOKEN_OPENID_";
    /**
     * Redis中key是token value 是       sessionkey
     */
    private static final String REDIS_TOKEN_SESSION_KEY = "TOKEN_SESSION_Key_";

    /**
     * 订单明细前缀
     */
    private static final String ORDERDATA = "ORDERDATA:";

    /**
     * 周末止前缀
     */
    private static final String WEEKENDVIP = "WEEKENDVIP";

    /**
     * 小程序token失效时间，默认2小时
     */
    private static final int WX_SECONDS = 1 * 3600;

    /**
     * token失效时间，默认7天
     */
    private static final int DEFAULT_SECONDS = 7 * 24 * 3600;

    /**
     * 信鸽推送ID的前缀
     */

    private static final String REDIS_XINGE = "XINGE_";
    /**
     * 私塾家微信token
     */
    private static final String SSJ_WX_TOKEN = "SSJ_WX_TOKEN";
    /**
     * 私塾家微信JStoken
     */
    private static final String SSJ_JSWX_TOKEN = "SSJ_JSWX_TOKEN";
    /**
     * 私塾家openId
     */
    private static final String SSJ_OPEN_ID = "SSJ_OPEN_ID";

    private static final String EASEMOBNAME = "easemobName";
    /**
     * 私塾家token失效时间，默认2小时
     */
    private static final int SSJ_SECONDS = 7000;
    @Autowired
    private JedisCluster jedisPool;
    private int seconds = DEFAULT_SECONDS;
    private int wxseconds = WX_SECONDS;
    private int ssjseconds = SSJ_SECONDS;




    public void setSeconds(int seconds) {
        this.seconds = seconds;
    }

    @Override
    public String createToken(String token,String userId,String type) {
        if(StringUtil.isBlank(token)){
            token=CodecUtil.createUUID();
        }
        String oldToken = get(formatTokenToken(userId+type));
        if (StringUtil.isNotBlank(oldToken)) {
            removeToken(oldToken,type);
        }
        set(formatTokenUserId(token), userId, seconds);
        set(formatTokenToken(userId+type), token, seconds);

        return token;
    }

    @Override
    public String createToken(String token,String userId,int seconds,String type) {
        if(StringUtil.isBlank(token)){
            token=CodecUtil.createUUID();
        }
        String oldToken = get(formatTokenToken(userId+type));
        if (StringUtil.isNotBlank(oldToken)) {
            removeToken(oldToken,type);
        }
        set(formatTokenUserId(token), userId, seconds);
        set(formatTokenToken(userId+type), token, seconds);

        return token;
    }

    @Override
    public String createToken(String token,String userId, String openId, int source,String sessionKey,String type) {
        if(StringUtil.isBlank(token)){
            token=CodecUtil.createUUID();
        }
        String oldToken = get(formatTokenToken(userId+type));
        if (StringUtil.isNotBlank(oldToken)) {
            removeToken(oldToken,userId,openId,type);
        }
        set(formatTokenUserId(token), userId, seconds);
        set(formatTokenToken(userId+type), token, seconds);//单独存放是为了后台了删除

        set(formatTokenOpenid(token),openId, seconds);
        set(formatTokenSessionKey(token),sessionKey, seconds);
        return token;
    }
    @Override
    public String createToken(String token,String userId, String openId, int source,String sessionKey,int seconds,String type) {
        if(StringUtil.isBlank(token)){
            token=CodecUtil.createUUID();
        }
        String oldToken = get(formatTokenToken(userId+type));
        if (StringUtil.isNotBlank(oldToken)) {
            removeToken(oldToken,userId,openId,type);
        }
        set(formatTokenUserId(token), userId, seconds);
        set(formatTokenToken(userId+type), token, seconds);//单独存放是为了后台了删除
        set(formatTokenOpenid(token),openId, seconds);
        set(formatTokenSessionKey(token),sessionKey, seconds);
        return token;
    }


    @Override
    public void removeToken(String token,String userId,String openId,String type){
        delete(formatTokenUserId(token));
        delete(formatTokenOpenid(token));
        delete(formatTokenSessionKey(token));
        delete(formatTokenToken(userId+type));
    }
    @Override
    public void delToken(String userId){
        List<String>  list = getToken(userId);
        for (String token : list) {
            if(token!=null){
                delete(formatTokenUserId(token));
                delete(formatTokenOpenid(token));
                delete(formatTokenSessionKey(token));
            }

        }
        delete(formatTokenToken(userId+"WX"));
        delete(formatTokenToken(userId+"APP"));
        delete(formatTokenToken(userId+"APP_TEACHER"));
        delete(formatTokenToken(userId+"IPAD"));
        delete(formatTokenToken(userId+"PC"));

    }

    @Override
    public void removeToken(String token) {
        if(StringUtil.isNotEmpty(token)){
            String userId =get(formatTokenUserId(token));
            delToken(userId);
        }

    }
    @Override
    public void removeToken(String token,String type) {
        if(StringUtil.isEmpty(token)){
            return ;

        }

        String replaceOpenid= get(formatTokenOpenid(token));
        if(replaceOpenid!=null){
            delete(formatTokenOpenid(token));
        }

        String replaceSessionKey= get(formatTokenSessionKey(token));
        if(replaceSessionKey!=null){
            delete(formatTokenSessionKey(token));
        }

        String replaceUserId =get(formatTokenUserId(token));
        if(replaceUserId!=null){
            delete(formatTokenToken(replaceUserId+type));
        }

        delete(formatTokenUserId(token));
    }

    @Override
    public boolean checkToken(String token) {
        boolean result = exists(formatTokenUserId(token));
        if (result) {
            refreshToken(token);
        }
        return result;
    }

    private void refreshToken(String token) {
        try {

            Long pptl=pttl(formatTokenUserId(token));
            if(pptl > 86400){
                return;
            }

            String userId = get(formatTokenUserId(token));
            boolean result = exists(formatTokenToken(userId + "PC"));
            if (result) {
                String redisToken = get(formatTokenToken(userId + "PC"));
                if (token.equals(redisToken)) {
                    expire(formatTokenUserId(token), seconds);
                    expire(formatTokenToken(userId + "PC"), seconds);
                }
            }
            result = exists(formatTokenToken(userId + "APP"));
            if (result) {
                String redisToken = get(formatTokenToken(userId + "APP"));
                if (token.equals(redisToken)) {
                    expire(formatTokenUserId(token), seconds);
                    expire(formatTokenToken(userId + "APP"), seconds);
                    expire(formatTokenOpenid(token), seconds);
                    expire(formatTokenSessionKey(token), seconds);
                }
            }
            result = exists(formatTokenToken(userId + "APP_TEACHER"));
            if (result) {
                String redisToken = get(formatTokenToken(userId + "APP_TEACHER"));
                if (token.equals(redisToken)) {
                    expire(formatTokenUserId(token), seconds);
                    expire(formatTokenToken(userId + "APP_TEACHER"), seconds);
                    expire(formatTokenOpenid(token), seconds);
                    expire(formatTokenSessionKey(token), seconds);
                }
            }
            result = exists(formatTokenToken(userId + "WX"));
            if (result) {
                String redisToken = get(formatTokenToken(userId + "WX"));
                if (token.equals(redisToken)) {
                    expire(formatTokenUserId(token), seconds);
                    expire(formatTokenToken(userId + "WX"), seconds);
                    expire(formatTokenOpenid(token), seconds);
                    expire(formatTokenSessionKey(token), seconds);
                }
            }


        } catch (Exception e) {
            logger.error("刷新token失效时间发生异常", e);
        }
    }

    @Override
    public boolean checkToken(String token, String userId) {
        boolean result = exists(formatTokenUserId(token));
        logger.info("result="+result);
        if (!userId.equals(get(formatTokenUserId(token)))) {
            result = false;
        }else{
            refreshToken(token);
        }

        return result;
    }

    /*
     * (non-Javadoc)
     */
    public String getUserId(String token) {
        return get(formatTokenUserId(token));
    }
    public String getOpenId(String token) {
        return get(formatTokenOpenid(token));
    }
    public String getSessionKey(String token) {
        return get(formatTokenSessionKey(token));
    }
    public List<String> getToken(String userId){
        List<String> list =new ArrayList<String>();
        String token =getToken( userId,"WX");
        if(token!=null){
            list.add(token);
        }
        token =getToken( userId,"APP");
        if(token!=null){
            list.add(token);
        }
        token =getToken( userId,"APP_TEACHER");
        if(token!=null){
            list.add(token);
        }

        return list;
    }
    public String getToken(String userId,String type) {
        return get(formatTokenToken(userId+type));
    }

    private String get(String key) {
    	
        return jedisPool.get(key);

    }

    public  <T> List<T> getList(String key, Class<T> clazz){


        String value = jedisPool.get(key);
        if(value!=null){
            return JSON.parseArray(value, clazz);
        }
        return null;

    }

    private Long ttl(String key) {
        return jedisPool.ttl(key);
    }

    private String set(String key, String value, int expireSeconds) {
        return jedisPool.setex(key, expireSeconds, value);
    }

    public boolean exists(String key) {
        return jedisPool.exists(key);
    }

    private void expire(String key, int seconds) {
        jedisPool.expire(key, seconds);
    }

    private void delete(String  keys) {
        jedisPool.del(keys);
    }

    public  <T> T get(String key,Class<T> clazz){

        String value = jedisPool.get(key);
        if(value!=null){
            return JSON.parseObject(value, clazz);
        }
        return null;
    }

    public String set(String key, Object  value, int expireSeconds) {

        String objectJson = JSON.toJSONString(value);
        return jedisPool.setex(key, expireSeconds, objectJson);

    }

    public String setAt(String key, long expireUnitTime,  Object  value) {

        String objectJson = JSON.toJSONString(value);
        String result = jedisPool.set(key, objectJson);
        jedisPool.expireAt(key, expireUnitTime);
        return result;
    }

    public String set(String key, Object  value) {

        String objectJson = JSON.toJSONString(value);
        return jedisPool.setex(key, DEFAULT_SECONDS, objectJson);

    }


    private Long pttl(String key) {
            return jedisPool.pttl(key);
    }

    public Long setnx(String key, String  value) {

        return jedisPool.setnx(key, value);

    }
    @Override
    public void hset(String key, Map<String, String> value) {

        jedisPool.hmset(key, value);
        jedisPool.expire(key, DEFAULT_SECONDS);

    }

    @Override
    public void hset(String key, Map<String, String> value, int expireSeconds) {

        jedisPool.hmset(key, value);
        jedisPool.expire(key, expireSeconds);

    }

    @Override
    public Map<String, String> hget(String key) {

        return jedisPool.hgetAll(key);

    }

    @Override
    public String setString(String key, String value, int expireSeconds) {
        return set(key, value, expireSeconds);
    }

    @Override
    public String setString(String key, String value) {
        return set(key, value,DEFAULT_SECONDS);
    }
    @Override
    public void delString(String key) {
        delete(key);
    }
    @Override
    public String getString(String key) {
        return get(key);
    }

    private String formatTokenToken(String key) {
        return REDIS_TOKEN_TOKEN_PREFIX.concat(key);
    }

    private String formatTokenUserId(String userId) {
        return REDIS_TOKEN_USERID_PREFIX.concat(userId);
    }

    private String formatTokenOpenid(String openid) {
        return REDIS_TOKEN_OPENID_PREFIX.concat(openid);
    }
    private String formatTokenSessionKey(String sessionKey) {
        return REDIS_TOKEN_SESSION_KEY.concat(sessionKey);
    }
    private String formatXingeKey(String key) {
        return REDIS_XINGE.concat(key);
    }
    /**
     * 私塾家公众号开始
     */
    @Override
    public String createSSJAccessToken(String wxToken, int seconds) {
        seconds = seconds >0 ? seconds:ssjseconds;
        set(SSJ_WX_TOKEN, wxToken, seconds);
        return wxToken;
    }

    @Override
    public String createSSJJSAccessToken(String wxToken, int seconds) {
        seconds = seconds >0 ? seconds:ssjseconds;
        set(SSJ_JSWX_TOKEN, wxToken, seconds);
        return wxToken;
    }
    private String formatOpenId(String key) {
        return SSJ_OPEN_ID.concat(key);
    }
    @Override
    public String createSSJOpenId(String openId, String subscribe) {
        String oldOpenId = get(formatOpenId(openId));
        if(oldOpenId !=null ){
            delete(formatOpenId(openId));
        }
        set(formatOpenId(openId), subscribe, DEFAULT_SECONDS);
        return openId;
    }

    @Override
    public String getSSJAccessToken() {
        String accessToken=get(SSJ_WX_TOKEN);
        if(accessToken ==null){
            int seconds  = 0;
            try {
                JSONObject json =new WeixinUtil().getAccessToken();
                accessToken=json.getString("access_token");
                seconds=json.getInteger("expires_in");
            } catch (Exception e) {
                logger.info("发生异常SSJAccessToken：" + e.getMessage());
                e.printStackTrace();
            }
            createSSJAccessToken(accessToken,seconds);
        }
        return accessToken;
    }

    @Override
    public String getSSJJSAccessToken() {
        String jsAccessToken=get(SSJ_JSWX_TOKEN);
        if(jsAccessToken ==null){
            int seconds  = 0;
            try {
                JSONObject json =new WeixinUtil().getJsApiTicket(this.getSSJAccessToken());
                jsAccessToken=json.getString("ticket");
                seconds=json.getInteger("expires_in");
            } catch (Exception e) {
                logger.info("发生异常：JsapiTicket" + e.getMessage());
                e.printStackTrace();
            }

            createSSJJSAccessToken(jsAccessToken,seconds);
        }
        return jsAccessToken;
    }



    @Override
    public String createWxOpenId(String openId, String subscribe) {
        String oldOpenId = get(formatOpenId(openId));
        if(oldOpenId !=null ){
            delete(formatOpenId(openId));
        }
        set(formatOpenId(openId), subscribe, DEFAULT_SECONDS);
        return openId;
    }

    @Override
    public String getWxOpenId(String openId) {
        return get(formatOpenId(openId));
    }

    @Override
    public String getLibWeather(String libKey,String city) {
        return get(libKey);
    }

    public String createWeather(String libValue,String libKey,int times){
        times = times >0 ? times:ssjseconds;
        set(libKey, libValue, times);
        return libValue;
    }

    @Override
    public String createPayOrderData(String orderNo, String orderdata) {
        String order=get(ORDERDATA+orderNo);
        if(order !=null){
            delete(ORDERDATA+orderNo);
        }
        set(ORDERDATA+orderNo, orderdata, wxseconds);
        return get(ORDERDATA+orderNo);
    }

    @Override
    public String getPayOrderData(String orderNo) {
        String order=get(ORDERDATA+orderNo);
        return order;
    }

    @Override
    public String getCurWeekendVipCode() {
        if(exists(WEEKENDVIP)){
            return get(WEEKENDVIP);
        }else{
            return "0";
        }

    }

    @Override
    public String createCurWeekendVipCode(String vipCode) {
        String weekend=get(WEEKENDVIP);
        if(weekend !=null){
            delete(WEEKENDVIP);
        }
        set(WEEKENDVIP, vipCode, 365 * 24 * 3600);
        return get(WEEKENDVIP);
    }

    @Override
    public String createManageType(String userId, String type) {
        String old_userId=get(userId);
        if(old_userId !=null){
            delete(userId);
        }
        set(userId,type, DEFAULT_SECONDS);
        return get(userId);
    }

    @Override
    public String getManageType(String userId) {
        if(exists(userId)){
            return get(userId);
        }else{
            return "-1";
        }
    }

    @Override
    public void deleteManageType(String userId) {
        String old_userId=get(userId);
        if(old_userId !=null){
            delete(userId);
        }
    }

    @Override
    public String createServiceCount(String vipIdAndLibId, String type) {
        String old_vipIdAndLibId=get(vipIdAndLibId);
        if(old_vipIdAndLibId !=null){
            delete(vipIdAndLibId);
        }
        set(vipIdAndLibId,type, 12 * 3600);
        return get(vipIdAndLibId);
    }

    @Override
    public String getServiceCount(String vipIdAndLibId) {
        if(exists(vipIdAndLibId)){
            return get(vipIdAndLibId);
        }else{
            return "-1";
        }
    }

    @Override
    public String getGrWord(String wordKey, int grade) {

        return get(wordKey);
    }

    public String createGrWord(String wordKey,String wordValue,int times){
        times = times >0 ? times:ssjseconds;
        set(wordKey, wordValue, times);
        return wordValue;
    }


    @Override
    public String setXingeAlias(String userId,String alias) {
        String oldAlias = get(formatXingeKey(userId));
        if (oldAlias != null) {
            delete(formatXingeKey(userId));
        }
        set(formatXingeKey(userId), alias, 365 * 24 * 3600);
        return alias;
    }

    @Override
    public String getXingeAlias(String userId) {
        return get(formatXingeKey(userId));
    }

    @Override
    public String setXinge(String derver, String xinge) {
        String oldAlias = get(formatXingeKey(derver));
        if (oldAlias != null) {
            delete(formatXingeKey(derver));
        }
        set(formatXingeKey(derver), xinge, 365 * 24 * 3600);
        return xinge;
    }

    @Override
    public String getXinge(String derver) {
        return get(formatXingeKey(derver));
    }

    @Override
    public boolean addLock(String key, String requestId, int expireSeconds) {
        String result = jedisPool.set(key, requestId, "NX", "EX", expireSeconds);
        return "OK".equals(result);
    }

    @Override
    public boolean unLock(String key, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedisPool.eval(script, Collections.singletonList(key), Collections.singletonList(requestId));
        return Objects.equals(1L, result);
    }

    @Override
    public int recordPasswordWrong(String userAccount, String clientType) {
        jedisPool.setex("LOGIN_" + userAccount + "_" + clientType + "_" + System.currentTimeMillis(), 30 * 60, "1");
        Set<String> keySet = this.getKeys("LOGIN_" + userAccount + "_" + clientType + "_*");
        if (keySet.size() >= 5) {
            jedisPool.setex("LOGIN_LOCK_" + userAccount + "_" + clientType + "_" + System.currentTimeMillis(), 30 * 60,
                    String.valueOf(System.currentTimeMillis()));
        }
        return keySet.size();
    }

    private Set<String> getKeys(String pattern) {
        Set<String> keySet = new HashSet<>();
        Map<String, JedisPool> clusterNodes = jedisPool.getClusterNodes();
        JedisPool tempJedisPool;
        for(String k : clusterNodes.keySet()){
            tempJedisPool = clusterNodes.get(k);
            try (Jedis connection = tempJedisPool.getResource()) {
                keySet.addAll(connection.keys(pattern));
            } catch (Exception e) {
                logger.error("Getting keys error", e);
            }
        }

        return keySet;
    }

    @Override
    public void clearPasswordWrongRecord(String userAccount) {
        this.batchDel("LOGIN_" + userAccount + "_*");
    }

    @Override
    public void batchDel(String key) {


        TreeSet<String> keys = new TreeSet<>();
        Map<String, JedisPool> clusterNodes = jedisPool.getClusterNodes();
        for(String k : clusterNodes.keySet()){
            logger.debug("Getting keys from: {}", k);
            JedisPool jp = clusterNodes.get(k);
            Jedis connection = jp.getResource();
            try {
                keys.addAll(connection.keys(key));
            } catch(Exception e){
                logger.error("Getting keys error: {}", e);
            } finally{
                logger.debug("Connection closed.");
                connection.close();//用完一定要close这个链接！！！
            }
        }
        Iterator<String> it = keys.iterator();
        while(it.hasNext()){
            String keyStr = it.next();
            jedisPool.del(keyStr);
        }
    }

    @Override
    public String setEasemob(String derver, String easemobName) {
        logger.info("reids-derver="+derver);
        logger.info("reids-easemobName="+easemobName);
        String oldEasemobName = get(EASEMOBNAME+derver);
        if (oldEasemobName != null) {
            logger.info("reids-oldeasemobName="+oldEasemobName);
            delete(EASEMOBNAME+derver);
        }
        set(EASEMOBNAME+derver, easemobName,10 * 365* 24 * 3600);
        return easemobName;
    }

    @Override
    public String getEasemob(String derver) {
        return get(EASEMOBNAME+derver);
    }

    @Override
    public Long incrKey(String key){
        return  jedisPool.incr(key);
    }
    @Override
    public Long incrByLong(String key,Long incrAmout){
        return  jedisPool.incrBy(key,incrAmout);
    }

    @Override
    public void refreshExpireTime(String key, int expireSeconds) {
        jedisPool.expire(key, expireSeconds);
    }

	@Override
	public Map<String,Object> getSSJAccessTokenAndTime() {
		Map<String,Object> data=new HashMap<String, Object>();
		String accessToken=get(SSJ_WX_TOKEN);
        if(accessToken ==null){
            int seconds  = 0;
            try {
                JSONObject json =new WeixinUtil().getAccessToken();
                accessToken=json.getString("access_token");
                seconds=json.getInteger("expires_in");
            } catch (Exception e) {
                logger.info("发生异常SSJAccessToken：" + e.getMessage());
                e.printStackTrace();
            }
            createSSJAccessToken(accessToken,seconds);
        }
        data.put("access_token", accessToken);
        data.put("expires_in", jedisPool.ttl(SSJ_WX_TOKEN).intValue());
        return data;
	}

	@Override
	public Map<String, Object> getSSJJSAccessTokenAndTime() {
		Map<String,Object> data=new HashMap<String, Object>();
		String jsAccessToken=get(SSJ_JSWX_TOKEN);
        if(jsAccessToken ==null){
            int seconds  = 0;
            try {
                JSONObject json =new WeixinUtil().getJsApiTicket(this.getSSJAccessToken());
                jsAccessToken=json.getString("ticket");
                seconds=json.getInteger("expires_in");
            } catch (Exception e) {
                logger.info("发生异常：JsapiTicket" + e.getMessage());
                e.printStackTrace();
            }

            createSSJJSAccessToken(jsAccessToken,seconds);
        }
        data.put("access_token", jsAccessToken);
        data.put("expires_in", jedisPool.ttl(SSJ_JSWX_TOKEN).intValue());
        return data;
	}
}
