package com.sunxung.factoring.service.sys.impl;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Map;

import com.auth0.jwt.interfaces.Claim;
import com.sunxung.factoring.component.constance.JwtTokenConstant;
import com.sunxung.factoring.component.enums.ClientTypeEnum;
import com.sunxung.factoring.component.util.JwtHS256Util;
import com.sunxung.factoring.component.util.TokenAndSessionCacheUtil;
import com.sunxung.factoring.service.sys.dto.LoginResultDto;
import com.sunxung.factoring.service.sys.dto.SessionDto;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.github.pagehelper.PageHelper;
import com.sunxung.factoring.component.cache.CacheKeyGenerator;
import com.sunxung.factoring.component.cache.RedisCache;
import com.sunxung.factoring.component.util.ValidatorUtil;
import com.sunxung.factoring.dict.impl.LogTypeDict;
import com.sunxung.factoring.entity.BaseSearch;
import com.sunxung.factoring.entity.GridPage;
import com.sunxung.factoring.entity.ValidatorEntity.ValidatorType;
import com.sunxung.factoring.entity.sys.LoginLog;
import com.sunxung.factoring.entity.sys.Session;
import com.sunxung.factoring.entity.sys.User;
import com.sunxung.factoring.mapper.sys.SessionMapper;
import com.sunxung.factoring.service.sys.DictionaryService;
import com.sunxung.factoring.service.sys.LoginLogService;
import com.sunxung.factoring.service.sys.SessionService;
import com.sunxung.factoring.service.sys.UserService;

@Service
public class SessionServiceImpl implements SessionService {

    @Autowired
    private SessionMapper sessionMapper;
    @Autowired
    RedisCache redisCache;
    @Autowired
    private LoginLogService loginLogService;
    @Autowired
    private UserService userService;
    @Autowired
    private DictionaryService dictionaryService;

    @Autowired
    private TokenAndSessionCacheUtil tokenAndSessionCacheUtil;

    @Transactional
    @Override
    public Session add(Session session) {
        ValidatorUtil.validator(ValidatorType.ADD, session);
        sessionMapper.add(session);
        return get(session.getId());
    }

    @Transactional
    @Override
    public Session update(Session session) {
        ValidatorUtil.validator(ValidatorType.UPDATE, session);
        sessionMapper.update(session);
        cacheSession(sessionMapper.get(session.getId()));
        return get(session.getId());
    }

    @Transactional
    @Override
    public boolean delete(Long id) {
        deleteRedisBySessionId(id);
        return sessionMapper.delete(id) > 0;
    }

    @Override
    public Session get(Long id) {
        if (null != id) {
            Session session = getRedisBySessionId(id);
            if (session == null) {
                session = sessionMapper.get(id);
                cacheSession(session);
            }
            return session;
        }
        return null;
    }

    @Override
    public List<Session> findAll() {
        return sessionMapper.findBySearch(null);
    }

    @Override
    public List<Session> findByUserId(Long userId) {
        return sessionMapper.findByUserId(userId);
    }

    @Override
    public GridPage<Session> findSessionsForPage(BaseSearch baseSearch) {
        PageHelper.startPage(baseSearch.getPage(), baseSearch.getRows()).size();
        List<Session> listSession = sessionMapper.findBySearch(baseSearch);
        Integer count = sessionMapper.countBySearch(baseSearch);
        return new GridPage<Session>(baseSearch.getRows(), baseSearch.getPage(), count, listSession);
    }

    /**
     * 修改缓存中的session信息
     * 
     * @param s
     */
    public void editRedisBySessionId(Session s) {
        Session session = (Session) redisCache.get(CacheKeyGenerator.generateCacheKeyFromId(Session.class, s.getId()));
        session.setUserId(s.getUserId());
        session.setLoginTime(new Date());
        session.setUrl(s.getUrl());
        cacheSession(session);
    }

    /**
     * 根据sessionId获取缓存中的session信息
     * 
     * @param sessionId
     * @return
     */
    public Session getRedisBySessionId(Long sessionId) {
        Session session = (Session) redisCache.get(CacheKeyGenerator.generateCacheKeyFromId(Session.class, sessionId));
        return session;
    }

    /**
     * 根据sessionId获取缓存中的session信息
     * 
     * @param sessionId
     * @return
     */
    public void deleteRedisBySessionId(Long sessionId) {
        redisCache.remove(CacheKeyGenerator.generateCacheKeyFromId(Session.class, sessionId));
    }

    /**
     * 缓存session
     * 
     * @param session
     */
    private void cacheSession(Session session) {
        if (session != null) {
            String cacheKey = CacheKeyGenerator.generateCacheKeyFromId(Session.class, session.getId());
            redisCache.set(cacheKey, session);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void timeout() {
        List<Session> sessions = findAll();
        for (Session session : sessions) {
            if (!session.isRemember() && session.isSessionTimeOut()) {
                loginInvalid(session);
                deleteRedisBySessionId(session.getId());
                sessionMapper.delete(session.getId());
            }
            if (session.isRemember() && session.isCookieSessionTimeOut()) {
                loginInvalid(session);
                deleteRedisBySessionId(session.getId());
                sessionMapper.delete(session.getId());
            }
        }
    }

    /**
     * 当session过期，用户自动退出登录时。往登录日志表中添加一条用户失效的记录
     *
     * @param session
     * @return void
     */
    private void loginInvalid(Session session) {
        if (null != session && null != session.getUserId()) {
            User user = userService.get(session.getUserId());
            if (null != user) {
                LoginLog log = new LoginLog(user);
                log.setLogType(dictionaryService.getByCode(LogTypeDict.ChildEnum.USER_FAILED.getCode()));
                loginLogService.add(log);
            }
        }
    }

    @Override
    public boolean deleteByUserId(Long userId) {
        return sessionMapper.deleteByUserId(userId) > 0;
    }

    @Override
    public int countBySearch() {
        int count = sessionMapper.countBySearch(new BaseSearch());
        return count;
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public LoginResultDto createTokenAndSession(ClientTypeEnum clientTypeEnum, User user) {
        //如果一个人管理到多个空间则baseUser里面的租户信息可能是空

        //第一步，判断用户以此种方式登陆是否已经存在过token如果存在过则需要移除以前的token
        SessionDto session = tokenAndSessionCacheUtil.getSession(user, clientTypeEnum.getCode());
        if (session == null) {
            session = new SessionDto();
            session.setAccount(user.getLoginName());
        } else {
            String oldToken = session.getToken();
//            User getUser = tokenAndSessionCacheUtil.getLoginUserByToken(oldToken);
//            if (user != null) {
//                //说明旧的token有用 但是 用户重新登陆用了新的token 保存提示到redis中
//                tokenAndSessionCacheUtil.setTokenAbnormalDestoryFlag(user.getUsername(),
//                    AbnormalDestoryTypeEnum.REPEAT_LOGIN);
//            }
            tokenAndSessionCacheUtil.removeTokenUser(oldToken);
        }
        //设置登陆时间
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String loginTime = dateTimeFormatter.format(LocalDateTime.now());
        session.setLoginTime(loginTime);
        //设置新的token
        String token = JwtHS256Util.buildJWT(clientTypeEnum, user);
        session.setToken(token);
        //将token 和对应的 用户信息存在 redis 中
        tokenAndSessionCacheUtil.token2User(clientTypeEnum.getCode(), token, user);
        tokenAndSessionCacheUtil.setSeeison(user, clientTypeEnum.getCode(),session);
        LoginResultDto loginResult = new LoginResultDto();
        loginResult.setToken(token);
        return loginResult;
    }

    @Override
    public void resetToken2User(String token, User user) {

    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void destory(String token) {
        if (Strings.isEmpty(token)) {
            return;
        }
        tokenAndSessionCacheUtil.removeTokenUser(token);
        Map<String, Claim> map = JwtHS256Util.getClaims(token);

        String account = null;

        String clientType = map.get(JwtTokenConstant.CLIENT_TYPE).asString();
        //account = map.getStringClaim(JwtTokenConstant.ACCOUNT);
        Long userId = map.get(JwtTokenConstant.USER_ID).asLong();
        if (userId != null) {
            tokenAndSessionCacheUtil.removeSessionOneType(userId, clientType, account);
        }
    }

    @Override
    public void destory(Long userId, List<String> account) {
        tokenAndSessionCacheUtil.destoryToken(userId, account);
    }

    @Override
    public User getLoginUserByToken(String token) {

        return tokenAndSessionCacheUtil.getLoginUserByToken(token);
    }
}
