package com.changfa.config.shiro;

import com.alibaba.fastjson.JSON;
import com.changfa.config.RedisConfig;
import com.changfa.config.constants.UserConstants;
import com.changfa.constants.CommomConstants;
import com.changfa.constants.RedisConstants;
import com.changfa.constants.ThreadContext;
import com.changfa.dto.UserBaseDto;
import com.changfa.utils.JwtUtil;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.crazycake.shiro.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.ObjectUtils;

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

/**
 * @author wwz
 */
public class CustomRedisSessionDAO extends RedisSessionDAO {

    private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
    private static final String DEFAULT_SESSION_KEY_PREFIX = "shiro:session:";
    private long sessionInMemoryTimeout = 1000L;
    private int expire = 7200;
    private RedisSerializer keySerializer = new StringSerializer();
    private RedisSerializer valueSerializer = new ObjectSerializer();
    private static ThreadLocal sessionsInThread = new ThreadLocal();
    private RedisConfig.ByteRedisTemplate<String, Byte[]> redisTemplate;
    private RedisTemplate stringRedisTemplate;

    public CustomRedisSessionDAO() {
    }

    @Override
    public int getExpire() {
        return expire;
    }

    @Override
    public void setExpire(int expire) {
        this.expire = expire;
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        this.saveSession(session);
    }

    public static UserBaseDto userBaseInfo(String sessionId) {
        return JSON.parseObject(JwtUtil.getTokenData(sessionId).toString(), UserBaseDto.class);
    }

    private void saveSession(Session session) throws UnknownSessionException {
        if (session != null && session.getId() != null) {
            if ((long) (this.expire * 1000) < session.getTimeout()) {
                logger.warn("Redis session expire time: " + this.expire * 1000 + " is less than Session timeout: " + session.getTimeout() + " . It may cause some problems.");
            }
            byte[] value = new byte[0];
            try {
                value = valueSerializer.serialize(session);
                Object tokenValue = session.getAttribute("account");
                UserBaseDto userBaseDto = null;
                try {
                    if (tokenValue == null || "".equals(tokenValue)) {
                        userBaseDto = userBaseInfo(session.getId().toString());
                        if (userBaseDto != null) {
                            tokenValue = userBaseDto.getUser().getAccount();
                        }
                    }
                } catch (Exception e) {
                    logger.error(e.getMessage());
                }
                if (tokenValue != null && !"".equals(tokenValue)) {
                    redisTemplate.opsForValue().set(this.getRedisSessionKey(session.getId()), value, this.expire, TimeUnit.SECONDS);
                    session.setTimeout(this.expire * 1000);
                    stringRedisTemplate.opsForValue().set(RedisConstants.UserCacheKey.USER_KEY.concat(tokenValue.toString()), session.getId().toString(), this.expire, TimeUnit.SECONDS);
                }else{
                    redisTemplate.opsForValue().set(this.getRedisSessionKey(session.getId()), value, this.expire, TimeUnit.SECONDS);
                }
                logger.info("===============>session time is {}", session.getTimeout());
            } catch (SerializationException e) {
                e.printStackTrace();
            }
        } else {
            logger.error("session or session id is null");
            throw new UnknownSessionException("session or session id is null");
        }
    }

    @Override
    public void delete(Session session) {
        if (session != null && session.getId() != null) {
            try {
                redisTemplate.delete(this.getRedisSessionKey(session.getId()));
                Object tokenValue = session.getAttribute("account");
                if (tokenValue != null && !"".equals(tokenValue)) {
                    stringRedisTemplate.delete(RedisConstants.UserCacheKey.USER_KEY.concat(tokenValue.toString()));
                }
            } catch (Exception var3) {
                logger.error("delete session error. session id=" + session.getId());
            }

        } else {
            logger.error("session or session id is null");
        }
    }

    @Override
    public Collection<Session> getActiveSessions() {
        HashSet sessions = new HashSet();

        try {
            Set<String> keys = redisTemplate.keys(CommomConstants.keyPrefix + "*");
            if (keys != null && keys.size() > 0) {
                Iterator i$ = keys.iterator();

                while (i$.hasNext()) {
                    String key = (String) i$.next();
                    Session s = (Session) valueSerializer.deserialize((byte[]) this.redisTemplate.opsForValue().get(key));
                    Object o = s.getAttribute(UserConstants.USER_KEY);
                    if (o != null) {
                        sessions.add(s);
                    }
                }
            }
        } catch (Exception var6) {
            logger.error("get active sessions error.");
        }

        return sessions;
    }

    @Override
    protected Serializable doCreate(Session session) {
        if (session == null) {
            logger.error("session is null");
            throw new UnknownSessionException("session is null");
        } else {
            ThreadContext threadContext = ThreadContext.getInstance();
            Object token = threadContext.get();
            Serializable sessionId = null;
            if (ObjectUtils.isEmpty(token)) {
                sessionId = this.generateSessionId(session);
            } else {
                sessionId = token.toString();
            }

            this.assignSessionId(session, sessionId);
            this.saveSession(session);
            return sessionId;
        }
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        if (sessionId == null) {
            logger.warn("session id is null");
            return null;
        } else {
            Session s = this.getSessionFromThreadLocal(sessionId);
            if (s != null) {
                return s;
            } else {
                logger.debug("read session from redis");
                try {
                    s = (Session) this.valueSerializer.deserialize((byte[]) this.redisTemplate.opsForValue().get(this.getRedisSessionKey(sessionId)));
                    this.setSessionToThreadLocal(sessionId, s);
                } catch (Exception var4) {
                    logger.error("read session error. settionId=" + sessionId);
                }
                return s;
            }
        }
    }

    private void setSessionToThreadLocal(Serializable sessionId, Session s) {
        Map<Serializable, SessionInMemory> sessionMap = (Map) sessionsInThread.get();
        if (sessionMap == null) {
            sessionMap = new HashMap();
            sessionsInThread.set(sessionMap);
        }

        SessionInMemory sessionInMemory = new SessionInMemory();
        sessionInMemory.setCreateTime(new Date());
        sessionInMemory.setSession(s);
        ((Map) sessionMap).put(sessionId, sessionInMemory);
    }

    private Session getSessionFromThreadLocal(Serializable sessionId) {
        Session s = null;
        if (sessionsInThread.get() == null) {
            return null;
        } else {
            Map<Serializable, SessionInMemory> sessionMap = (Map) sessionsInThread.get();
            SessionInMemory sessionInMemory = (SessionInMemory) sessionMap.get(sessionId);
            if (sessionInMemory == null) {
                return null;
            } else {
                Date now = new Date();
                long duration = now.getTime() - sessionInMemory.getCreateTime().getTime();
                if (duration < this.sessionInMemoryTimeout) {
                    s = sessionInMemory.getSession();
                    logger.debug("read session from memory");
                } else {
                    sessionMap.remove(sessionId);
                }

                return s;
            }
        }
    }

    private String getRedisSessionKey(Serializable sessionId) {
        return CommomConstants.keyPrefix + sessionId;
    }

    public RedisConfig.ByteRedisTemplate getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisConfig.ByteRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void setStringRedisTemplate(RedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
}
