package me.chyxion.tigon.shiro.cache.shiro;

import java.util.Set;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import org.springframework.util.Assert;
import org.apache.shiro.session.Session;
import org.springframework.data.redis.core.*;
import org.apache.shiro.cache.CacheException;
import me.chyxion.tigon.shiro.model.AuthUser;
import me.chyxion.tigon.shiro.cache.SessionCache;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * You may find session by user id
 * @version 0.0.1
 * @since 0.0.1
 * @author Shaun Chyxion <br>
 * chyxion@163.com <br>
 * May 12, 2016 1:25:02 PM
 */
@Slf4j
public class UserIdKeyRedisSessionCache implements SessionCache {

    @Autowired
    @Qualifier("redisTemplate")
    protected RedisTemplate<Object, Object> redisTpl;
    @Value("${tigon.shiro.session.redis.prefix:AUTH_SESSION}")
    protected String prefix;
    protected String userIdSessionPrefix;

    /**
     * init
     */
    @PostConstruct
    void init() {
        userIdSessionPrefix = prefix + ":USER";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Session get(Serializable sessionId) {
        log.info("Redis Session Cache Get [{}].", sessionId);
        Session session = (Session) valueOp().get(prefix + ":" + sessionId);
        log.debug("Session [{}] Found.", session);
        return session;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Session put(Session session) {
        return put(session.getId(), session);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Session put(Serializable sessionId, Session session) {
        log.info("Redis session cache put [{}:{}:{}].", prefix, sessionId, session);
        valueOp().set(prefix + ":" + sessionId,
            session,
            session.getTimeout(),
            TimeUnit.MILLISECONDS);
        return session;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Session remove(Serializable sessionId) {
        log.info("Redis session cache remove [{}].", sessionId);
        Session session = get(sessionId);
        if (session != null) {
            log.info("Redis session cache remove [{}].", session);
            redisTpl.delete(prefix + ":" + sessionId);
        }
        return session;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clear() throws CacheException {
        log.warn("Redis session cache #clear not supported");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int size() {
        Long size = hashOp().size(userIdSessionPrefix);
        return size != null ? size.intValue() : 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<Serializable> keys() {
        log.warn("Redis session cache #keys not supported");
        return Collections.emptySet();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<Session> values() {
        log.warn("Redis session cache #values not supported");
        return Collections.emptyList();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Session findByUserId(final Serializable userId) {
        log.debug("Find session by user id [{}].", userId);
        Serializable sessionId = hashOp().get(userIdSessionPrefix, userId);
        Session session = null;
        if (sessionId != null) {
            log.debug("User [{}] session id [{}] found.", userId, sessionId);
            session = get(sessionId);
            log.info("User [{}] session [{}] found.", userId, session);
            if (session == null) {
                log.info("No user [{}] session found, remove.");
                hashOp().delete(userIdSessionPrefix, userId);
            }
        }
        return session;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Session putUserSession(Session session) {
        log.info("Put user session [{}].", session);
        AuthUser<?> authUser = AuthUser.get(session);
        Assert.state(authUser != null, "Auth user not found in session");
        final Serializable userId = authUser.getUserId();
        Assert.state(userId != null, "Session auth user id could not be null");
        log.info("Put user [{}] session [{}].", userId, session);
        hashOp().put(userIdSessionPrefix, userId, session.getId());
        return session;
    }

    // --
    // private methods
    protected HashOperations<Object, Serializable, Serializable> hashOp() {
        return redisTpl.<Serializable, Serializable>opsForHash();
    }

    protected ValueOperations<Object, Object> valueOp() {
        return redisTpl.opsForValue();
    }
}
