package org.superboot.common.pub;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.superboot.base.BaseConstants;
import org.superboot.base.BaseException;
import org.superboot.base.BaseToken;
import org.superboot.base.StatusCode;
import org.superboot.entity.dto.UserMenuDTO;
import org.superboot.entity.dto.UserResourceDTO;
import org.superboot.utils.DateUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <b> 全局Redis工具类 </b>
 * <p>
 * 功能描述: 提供基于Redis的常用操作
 * </p>
 */
@Component
public class Pub_RedisUtils {


    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private Pub_Tools pubTools;

    /**
     * Token的状态,后期如果需要踢出用户则直接将此状态改为 Y
     */
    public static final String TOKEN_STATUS = "TokenStatus";

    /**
     * 锁定TOKEN
     */
    public static final String TOKEN_STATUS_LOCKED = "Y";

    /**
     * TOKEN未锁定
     */
    public static final String TOKEN_STATUS_UNLOCKED = "N";

    /**
     * Token内存储的详细信息
     */
    public static final String TOKEN_ITEM = "TokenItem";

    /**
     * TOKEN数据过期时间 默认15天如果15天内用户没有进行过登陆会销毁TOKEN缓存信息
     */
    public static final int TOKEN_EXPIRED_DAY = 15;

    /**
     * 用户权限信息
     */
    public static final String USER_RESOURCE = "UserResource";

    /**
     * 用户菜单信息
     */
    public static final String USER_MENU = "UserMenu";


    /**
     * 获取Redis操作实例
     *
     * @return
     */
    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }


    /**
     * 存储第三方请求消息ID与Token对照关系 默认5分钟有效
     *
     * @param messageId 消息ID
     * @param oToken    第三方授权TOKEN
     * @return
     */
    public void setOtherMessId(String messageId, String oToken) {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        operations.set(messageId, oToken, 5, TimeUnit.MINUTES);
    }

    /**
     * 根据第三方TOKEN信息获取公司主键
     *
     * @param messageId 第三方消息ID
     * @return
     */
    public Long getPkGroupByOtherMessageId(String messageId) {
        if (StringUtils.isNotBlank(messageId)) {
            ValueOperations<String, String> operations = redisTemplate.opsForValue();
            boolean exists = redisTemplate.hasKey(messageId);
            if (exists) {
                String token = operations.get(messageId);

                return getPkGroupByOtherToken(token);
            }
        }

        return null;
    }


    /**
     * 设置第三方请求TOKEN与公司对照关系,默认有效期5分组
     *
     * @param oToken  TOKEN信息
     * @param pkGroup 公司主键
     */
    public void setOtherToken(String oToken, Long pkGroup) {
        ValueOperations<String, Long> operations = redisTemplate.opsForValue();
        operations.set(oToken, pkGroup, 5, TimeUnit.MINUTES);
    }


    /**
     * 根据第三方TOKEN信息获取公司主键
     *
     * @param oToken 授权TOKEN
     * @return
     */
    public Long getPkGroupByOtherToken(String oToken) {
        if (StringUtils.isNotBlank(oToken)) {
            ValueOperations<String, Long> operations = redisTemplate.opsForValue();
            boolean exists = redisTemplate.hasKey(oToken);
            if (exists) {
                return operations.get(oToken);
            } else {
                pubTools.checkOtherToken(oToken);
                return operations.get(oToken);
            }
        }

        return null;
    }

    /**
     * 设置会话的用户信息,默认保持时间为5分钟,如果后期涉及到长时间的操作,再调整超时时间
     *
     * @param messageId 会话消息ID
     * @param token     用户信息
     * @return
     */
    public boolean setSessionInfo(String messageId, BaseToken token) {
        //构造信息
        ValueOperations<String, String> operations = redisTemplate.opsForValue();

        boolean exists = redisTemplate.hasKey(messageId);
        if (exists) {
            return true;
        } else {
            operations.set(messageId, pubTools.objectToJson(token), 5, TimeUnit.MINUTES);
            return true;
        }

    }

    /**
     * 根据会话消息ID获取用户信息
     *
     * @param messageId 会话消息ID
     * @return
     */
    public BaseToken getSessionInfo(String messageId) {
        if (StringUtils.isNotBlank(messageId)) {
            ValueOperations<String, String> operations = redisTemplate.opsForValue();
            boolean exists = redisTemplate.hasKey(messageId);
            if (exists) {
                return pubTools.jsonToObject(operations.get(messageId), BaseToken.class);
            }
        }
        return null;
    }

    /**
     * 设置登陆用户信息
     *
     * @param token     登陆TOKEN
     * @param baseToken 用户身份信息
     * @return
     */
    public BaseToken setTokenInfo(String token, BaseToken baseToken) {
        ValueOperations<String, HashMap> operations = redisTemplate.opsForValue();
        //如果存在则先删除
        boolean exists = redisTemplate.hasKey(token);
        if (exists) {
            //更新用户最后登陆信息
            HashMap tokenItem = operations.get(token);
            tokenItem.put(TOKEN_ITEM, pubTools.objectToJson(baseToken));
            operations.set(token, tokenItem, TOKEN_EXPIRED_DAY, TimeUnit.DAYS);
        } else {
            //将用户信息存储到Redis中
            HashMap tokenItem = new HashMap(2);
            tokenItem.put(TOKEN_STATUS, TOKEN_STATUS_UNLOCKED);
            tokenItem.put(TOKEN_ITEM, pubTools.objectToJson(baseToken));
            operations.set(token, tokenItem, TOKEN_EXPIRED_DAY, TimeUnit.DAYS);
        }


        return baseToken;
    }

    /**
     * 根据TOKEN获取用户信息,每次调用都会刷新有效期
     *
     * @param token token
     * @return
     */
    public BaseToken getTokenInfo(String token) {
        if (StringUtils.isNotBlank(token)) {
            ValueOperations<String, HashMap> operations = redisTemplate.opsForValue();
            boolean exists = redisTemplate.hasKey(token);
            if (exists) {
                //更新用户最后登陆信息
                HashMap tokenItem = operations.get(token);
                //获取具体内容
                BaseToken baseToken = null;
                if (tokenItem.get(Pub_RedisUtils.TOKEN_ITEM) instanceof BaseToken) {
                    baseToken = (BaseToken) tokenItem.get(Pub_RedisUtils.TOKEN_ITEM);
                } else {
                    baseToken = pubTools.jsonToObject(tokenItem.get(Pub_RedisUtils.TOKEN_ITEM) + "", BaseToken.class);
                }

                if (null != tokenItem) {
                    //如果TOKEN已经锁定,则提示用户需要重新登陆
                    if (TOKEN_STATUS_LOCKED.equals(tokenItem.get(TOKEN_STATUS))) {
                        baseToken.setErrCode(StatusCode.TOKEN_LOCKED);
                    } else {
                        baseToken.setLoginTime(DateUtils.getCurrentTime());
                        baseToken.setLoginDate(DateUtils.getCurrentDate());
                        tokenItem.put(TOKEN_ITEM, pubTools.objectToJson(baseToken));
                        //每次用户登陆都刷新TOKEN的有效期
                        operations.set(token, tokenItem, TOKEN_EXPIRED_DAY, TimeUnit.DAYS);
                    }
                    return baseToken;
                }

            }
        }

        return null;
    }


    /**
     * 设置TOKEN白名单,KEY 为
     *
     * @param pkUser   用户主键
     * @param platform 来源平台
     * @param version  来源版本
     * @param token    TOKEN信息
     */
    public void setTokenAllow(long pkUser, String platform, String version, String token) {
        ValueOperations<String, HashMap> operations = redisTemplate.opsForValue();
        boolean exists = redisTemplate.hasKey(BaseConstants.USER_TOKEN + "-" + pkUser);
        if (exists) {
            HashMap<String, Object> tokenMap = operations.get(BaseConstants.USER_TOKEN + "-" + pkUser);
            //添加新的TOKEN信息到缓存中
            if (!tokenMap.containsKey(token)) {
                //TOKEN详细信息
                HashMap<String, String> itemMap = new HashMap(2);
                itemMap.put(BaseConstants.TOKEN_VERSION, version);
                itemMap.put(BaseConstants.TOKEN_PLATFORM, platform);
                tokenMap.put(token, itemMap);
            }
            //添加新的版本与平台信息到缓存中
            if (!tokenMap.containsKey(platform + "-" + version)) {
                tokenMap.put(platform + "-" + version, token);
            }

            //清理历史
            redisTemplate.delete(BaseConstants.USER_TOKEN + "-" + pkUser);
            //重新设置以便刷新最后失效时间
            operations.set(BaseConstants.USER_TOKEN + "-" + pkUser, tokenMap, TOKEN_EXPIRED_DAY, TimeUnit.DAYS);
        } else {
            //TOKEN详细信息
            HashMap<String, String> itemMap = new HashMap(2);
            itemMap.put(BaseConstants.TOKEN_VERSION, version);
            itemMap.put(BaseConstants.TOKEN_PLATFORM, platform);
            HashMap<String, Object> tokenMap = new HashMap<>(1);
            tokenMap.put(token, itemMap);
            tokenMap.put(platform + "-" + version, token);

            //重新设置以便刷新最后失效时间
            operations.set(BaseConstants.USER_TOKEN + "-" + pkUser, tokenMap, TOKEN_EXPIRED_DAY, TimeUnit.DAYS);
        }
    }

    /**
     * 根据来源平台与版本获取TOKEN
     *
     * @param pkUser   用户主键
     * @param platform 来源平台
     * @param version  来源版本
     * @return
     */
    public String getTokenAllow(long pkUser, String platform, String version) {
        ValueOperations<String, HashMap> operations = redisTemplate.opsForValue();
        boolean exists = redisTemplate.hasKey(BaseConstants.USER_TOKEN + "-" + pkUser);
        if (exists) {
            HashMap<String, Object> tokenMap = operations.get(BaseConstants.USER_TOKEN + "-" + pkUser);
            //循环处理TOKEN白名单里失效的TOKEN
            for (String key : tokenMap.keySet()) {
                //判断对象是TOKEN
                if (key.startsWith("Bearer")) {
                    //如果TOKEN已经不存在了
                    if (!redisTemplate.hasKey(key)) {
                        //清理失效TOKEN信息
                        cleanTokenAllow(pkUser, key);
                    }
                }
            }
            if (tokenMap.containsKey(platform + "-" + version)) {
                return "" + tokenMap.get(platform + "-" + version);
            }
        }
        return null;
    }

    /**
     * 根据TOKEN获取来源平台与版本
     *
     * @param pkUser 用户主键
     * @param token  TOKEN
     * @return
     */
    public HashMap<String, String> getTokenAllowItem(long pkUser, String token) {
        ValueOperations<String, HashMap> operations = redisTemplate.opsForValue();
        boolean exists = redisTemplate.hasKey(BaseConstants.USER_TOKEN + "-" + pkUser);
        if (exists) {
            HashMap<String, Object> tokenMap = operations.get(BaseConstants.USER_TOKEN + "-" + pkUser);
            if (tokenMap.containsKey(token)) {
                return (HashMap<String, String>) tokenMap.get(token);
            }
        }
        return null;
    }


    /**
     * 清理全部TOKEN信息(多用于修改密码后)
     *
     * @param pkUser 用户主键
     */
    public void cleanAllToken(long pkUser) {
        //清理白名单与TOKEN信息
        ValueOperations<String, HashMap> operations = redisTemplate.opsForValue();
        boolean exists = redisTemplate.hasKey(BaseConstants.USER_TOKEN + "-" + pkUser);
        if (exists) {
            HashMap<String, Object> tokenMap = operations.get(BaseConstants.USER_TOKEN + "-" + pkUser);
            //循环处理TOKEN白名单里失效的TOKEN
            for (String key : tokenMap.keySet()) {
                //判断对象是TOKEN
                if (key.startsWith("Bearer")) {
                    redisTemplate.delete(key);
                }
            }
            redisTemplate.delete(BaseConstants.USER_TOKEN + "-" + pkUser);
        }
    }

    /**
     * 根据来源平台+版本清理TOKEN白名单
     *
     * @param pkUser   用户主键
     * @param platform 来源平台
     * @param version  来源版本
     */
    public void cleanTokenAllow(long pkUser, String platform, String version) {
        ValueOperations<String, HashMap> operations = redisTemplate.opsForValue();
        boolean exists = redisTemplate.hasKey(BaseConstants.USER_TOKEN + "-" + pkUser);
        if (exists) {
            HashMap<String, Object> tokenMap = operations.get(BaseConstants.USER_TOKEN + "-" + pkUser);
            String token = "" + tokenMap.get(platform + "-" + version);
            tokenMap.remove(token);
            tokenMap.remove(platform + "-" + version);
            //重新设置以便刷新最后失效时间
            operations.set(BaseConstants.USER_TOKEN + "-" + pkUser, tokenMap, TOKEN_EXPIRED_DAY, TimeUnit.DAYS);
        }
    }

    /**
     * 根据token清理TOKEN白名单
     *
     * @param pkUser 用户主键
     * @param token  TOKEN信息
     */
    public void cleanTokenAllow(long pkUser, String token) {
        ValueOperations<String, HashMap> operations = redisTemplate.opsForValue();
        boolean exists = redisTemplate.hasKey(BaseConstants.USER_TOKEN + "-" + pkUser);
        if (exists) {
            HashMap<String, Object> tokenMap = operations.get(BaseConstants.USER_TOKEN + "-" + pkUser);
            HashMap<String, String> itemMap = (HashMap<String, String>) tokenMap.get(token);
            tokenMap.remove(token);
            //判断TOKEN的信息与平台版本存储的一致才进行删除,理论上会一致,但是不排除出现同平台的信息不一致的情况
            String tokenStr = "" + tokenMap.get(itemMap.get(BaseConstants.TOKEN_PLATFORM) + "-" + itemMap.get(BaseConstants.TOKEN_VERSION));
            if (token.equals(tokenStr)) {
                tokenMap.remove(itemMap.get(BaseConstants.TOKEN_PLATFORM) + "-" + itemMap.get(BaseConstants.TOKEN_VERSION));
            }
            //重新设置以便刷新最后失效时间
            operations.set(BaseConstants.USER_TOKEN + "-" + pkUser, tokenMap, TOKEN_EXPIRED_DAY, TimeUnit.DAYS);
        }
    }

    /**
     * 设置Token的锁定状态
     *
     * @param token 用户Token
     */
    public void tokenLocked(String token, boolean locked) {
        ValueOperations<String, HashMap> operations = redisTemplate.opsForValue();
        boolean exists = redisTemplate.hasKey(token);
        if (exists) {
            HashMap tokenItem = operations.get(token);
            if (locked) {
                tokenItem.put(TOKEN_STATUS, TOKEN_STATUS_LOCKED);
            } else {
                tokenItem.put(TOKEN_STATUS, TOKEN_STATUS_UNLOCKED);
            }
            operations.set(token, tokenItem, TOKEN_EXPIRED_DAY, TimeUnit.DAYS);
        }
    }


    /**
     * 设置用户权限信息
     *
     * @param userId          用户ID
     * @param resourceHashMap 用户权限信息MAP KEY为模块ID+类名+方法名
     * @return
     */
    public void setUserResource(long userId, HashMap<String, UserResourceDTO> resourceHashMap) {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        if (redisTemplate.hasKey(USER_RESOURCE + userId)) {
            redisTemplate.delete(USER_RESOURCE + userId);
        }
        operations.set(USER_RESOURCE + userId, pubTools.objectToJson(resourceHashMap));
    }

    /**
     * 获取用户权限信息
     *
     * @param userId 用户ID
     * @return
     */
    public JSONObject getUserResource(long userId) {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        boolean exists = redisTemplate.hasKey(USER_RESOURCE + userId);
        if (exists) {

            return pubTools.jsonToObject(operations.get(USER_RESOURCE + userId), JSONObject.class);
        }
        return null;
    }


    /**
     * 设置用户菜单
     *
     * @param userId   用户主键
     * @param menuList 用户菜单列表
     */
    public void setUserMenu(long userId, List<UserMenuDTO> menuList) {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        if (redisTemplate.hasKey(USER_MENU + userId)) {
            redisTemplate.delete(USER_MENU + userId);
        }

        if (null == menuList) {
            redisTemplate.delete(USER_MENU + userId);
        } else {
            operations.set(USER_MENU + userId, pubTools.objectToJson(menuList));
        }
    }


    /**
     * 获取用户菜单
     *
     * @param userId 用户ID
     * @return
     */
    public List getUserMenu(long userId) {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        boolean exists = redisTemplate.hasKey(USER_MENU + userId);
        if (exists) {

            Object list = operations.get(USER_MENU + userId);
            if (list instanceof List) {
                return (List<UserMenuDTO>) list;
            }

            Object o = JSON.parse(operations.get(USER_MENU + userId));

            List<UserMenuDTO> rList = new ArrayList<>();

            List data = JSON.toJavaObject((JSON) o, List.class);

            for (int i = 0; i < data.size(); i++) {
                UserMenuDTO item = ((JSONObject) data.get(i)).toJavaObject(UserMenuDTO.class);
                rList.add(item);

            }
            return rList;
        }
        return null;
    }

    /**
     * 通过请求信息获取用户TOKEN信息
     *
     * @param request 请求信息
     * @return
     */
    public BaseToken getTokenByRequest(HttpServletRequest request) {
        BaseToken baseToken = null;
        //获取用户TOKEN信息,默认情况均通过消息ID获取
        String token = request.getHeader(BaseConstants.GLOBAL_KEY.toLowerCase());
        if (StringUtils.isBlank(token)) {
            //消息ID未获取到的时候,多数是内部测试阶段,此时使用Token进行获取
            token = request.getHeader(BaseConstants.TOKEN_KEY);
            if (StringUtils.isBlank(token)) {
                throw new BaseException(StatusCode.TOKEN_INVALID);
            } else {
                baseToken = getTokenInfo(token);
            }
        } else {
            baseToken = getSessionInfo(token);
        }
        if (null == baseToken) {
            throw new BaseException(StatusCode.TOKEN_LOCKED);
        }
        //判断获取TOKEN,TOKEN本身存在问题,比如已经封存
        if (null != baseToken.getErrCode()) {
            throw new BaseException(baseToken.getErrCode());
        }

        return baseToken;
    }

    /**
     * 通过请求信息获取用户第三方授权公司
     *
     * @param request 请求信息
     * @return
     */
    public Long getPkGroupByRequest(HttpServletRequest request) {
        //获取第三方公司信息,默认情况均通过消息ID获取
        String token = request.getHeader(BaseConstants.OTHER_MESSAGE_ID.toLowerCase());
        if (StringUtils.isBlank(token)) {
            //消息ID未获取到的时候,多数是内部测试阶段,此时使用Token进行获取
            token = request.getHeader(BaseConstants.OTHER_TOKEN_KEY);
            if (StringUtils.isBlank(token)) {
                token = request.getHeader(BaseConstants.GLOBAL_KEY.toLowerCase());
                if (StringUtils.isBlank(token)) {
                    token = request.getHeader(BaseConstants.TOKEN_KEY);
                    if (StringUtils.isNotBlank(token)) {
                        return getTokenInfo(token).getPkGroup();
                    }

                } else {
                    return getSessionInfo(token).getPkGroup();
                }


            } else {
                return getPkGroupByOtherToken(token);
            }
        } else {
            return getPkGroupByOtherMessageId(token);
        }

        //返回一个不存在的ID
        return -99L;
    }


}