package cn.quantgroup.xyqb.service.session.impl;

import cn.quantgroup.user.enums.RecordType;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.model.AuthBean;
import cn.quantgroup.xyqb.model.LoginProperties;
import cn.quantgroup.xyqb.model.UserStatistics;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.model.session.SessionValue;
import cn.quantgroup.xyqb.service.session.ISessionService;
import cn.quantgroup.xyqb.service.session.aspect.UserBtRegisterFill;
import cn.quantgroup.xyqb.service.user.ILoginRecordService;
import cn.quantgroup.xyqb.util.MqUtils;
import cn.quantgroup.xyqb.util.TenantUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * session创建和续期
 *
 */
@Slf4j
@Service
public class SessionServiceImpl implements ISessionService {

    @Resource
    private RedisTemplate<String, String> stringRedisTemplate;

    @Resource
    private ILoginRecordService loginRecordService;

    @Value("${token.prefix}")
    private String prefix;

    /**
     * 更新session
     * 用户信息存在,更新session中的最后访问时间,重新写入缓存.
     * 存在则保存用户session信息,userId为uuid
     *
     * @param user
     * @return
     */
    @Override
    public AuthBean createSession(User user, LoginProperties properties) {
        //找到用户
        //TODO: 使用userId
        String sessionId = findSessionIdByUserIdLoginProperties(user.getId(), properties);
        SessionStruct sessionStruct = null;
        if (StringUtils.isNotEmpty(sessionId)) {
            sessionStruct = findSessionBySessionId(sessionId);
        }
        if (Objects.nonNull(sessionStruct) && Objects.nonNull(properties)) {
            sessionStruct.setAttribute("channelId", String.valueOf(properties.getChannelId()));
            sessionStruct.setAttribute("createdFrom", String.valueOf(properties.getCreatedFrom()));
            sessionStruct.setAttribute("appChannel", String.valueOf(properties.getAppChannel()));
            sessionStruct.getValues().setLoginProperties(properties);
            persistSession(sessionStruct.getSid(), sessionStruct.getValues());
        } else {
            sessionStruct = createSessionAndPersist(user, properties);
        }
        String uuid = user.getUuid();
        AuthBean authBean = new AuthBean();
        authBean.setPhoneNo(user.getPhoneNo());
        authBean.setToken(sessionStruct.getSid());
        authBean.setUuid(uuid);
        authBean.setHasPassword(user.getHasPassword());
        log.info("用户登录成功, loginFrom:{}, phoneNo:{},appChannel:{},channelId:{}", properties.getCreatedFrom(), user.getPhoneNo(), properties.getAppChannel(), properties.getChannelId());

        // 添加登陆日志
        loginRecordService.saveLoginRecord(user.getId(), RecordType.LOGINRECORD.getName());

        return authBean;
    }

    @Override
    public SessionStruct createSessionAndPersist(User user, LoginProperties properties) {
        SessionStruct sessionStruct;
        //获取sessionid
        String sessionId = findSessionIdByUserIdLoginProperties(user.getId(), properties);
        if (StringUtils.length(sessionId) > 0) {
            sessionStruct = findSessionBySessionId(sessionId);
            if (sessionStruct == null) {
                sessionStruct = newSession(user, properties);
            } else {
                sessionStruct.getValues().setLoginProperties(properties);
            }
            persistSession(sessionStruct.getSid(), sessionStruct.getValues());
        } else {
            sessionStruct = newSession(user, properties);
            persistSession(sessionStruct.getSid(), sessionStruct.getValues());
        }
        return sessionStruct;
    }

    private String findSessionIdByUserIdLoginProperties(Long userId, LoginProperties properties) {
        return stringRedisTemplate.opsForValue().get(generateLoginPropertiesKey(userId, properties));
    }

    /**
     * 每个渠道都有不同的 session
     *
     * @param userId     用户 ID
     * @param properties baitiao/xyqb/vcc ... + 用户注册来源
     * @return redisKey. 用来标识这个渠道的用户 Session 是否存在
     */
    private String generateLoginPropertiesKey(Long userId, LoginProperties properties) {
        if (ObjectUtils.isEmpty(properties.getTenantId())) {
            return Constants.Session.USER_SESSION_ID_CACHE + ":" + userId + ":" + properties.getMerchantName() + ":" + properties.getCreatedFrom();
        }else if (properties.getTenantId().equals(0) || TenantUtil.TENANT_DEFAULT.equals(properties.getTenantId())) {
            return Constants.Session.USER_SESSION_ID_CACHE + ":" + userId + ":" + properties.getMerchantName() + ":" + properties.getCreatedFrom();
        }else {
            return Constants.Session.USER_SESSION_ID_CACHE + ":" + userId + ":" + properties.getMerchantName() + ":" + properties.getCreatedFrom() + ":" + properties.getTenantId();
        }
    }

    private String findSessionValueBySessionId(String sessionId) {
        String result = stringRedisTemplate.opsForValue().get(Constants.Session.USER_SESSION_CACHE + sessionId);
        return StringUtils.defaultString(result, "");
    }

    private SessionStruct newSession(User user, LoginProperties properties) {
        Timestamp now = new Timestamp(System.currentTimeMillis());
        SessionStruct sessionStruct = new SessionStruct();
        SessionValue sessionValue = new SessionValue();
        sessionStruct.setSid(prefix + UUID.randomUUID().toString());
        sessionValue.setCreatedAt(now);
        sessionValue.setLastAccessTime(now);
        sessionValue.setUser(user);
        sessionValue.setLoginProperties(properties);
        Map<String, String> values = new HashMap<>();
        sessionValue.setValues(values);
        sessionStruct.setValues(sessionValue);
        // 发送登陆成功统计消息
        /*
         * 部分免密渠道登录统计，用户中心不需识别，由统计平台来过滤
         * 贷款导航(84660)；壹账通H5(159384)
         */
        User userRereset = new User();
        BeanUtils.copyProperties(user, userRereset);
        userRereset.setRegisteredFrom(properties.getCreatedFrom());
        UserStatistics statistics = new UserStatistics(userRereset, null, properties.getAction(), properties.getChannelId());
        // 推送老的登陆统计信息
        MqUtils.sendLoanVest(statistics);
        return sessionStruct;
    }

    @Override
    @UserBtRegisterFill
    public void persistSession(String token, SessionValue sessionValue) {
        persistSession(token, sessionValue, Constants.Session.SESSION_VALID_TIME);
    }

    @Override
    public void persistSession(String token, SessionValue sessionValue, Long time) {
        Timestamp current = new Timestamp(System.currentTimeMillis());
        sessionValue.setLastAccessTime(current);
        String json = JSON.toJSONString(sessionValue);
        stringRedisTemplate.opsForValue().set(Constants.Session.USER_SESSION_CACHE + token, json,
                time, TimeUnit.SECONDS);
        String key = generateLoginPropertiesKey(sessionValue.getUser().getId(), sessionValue.getLoginProperties());
        stringRedisTemplate.opsForValue().set(key, token, time, TimeUnit.SECONDS);
        log.info("[Session生命期延续],token:{},有效期:[24Hour]", token);
        setUserIdTokenKeys(sessionValue.getUser().getId(), key);
    }

    /**
     * 设置用户token集合方便注销使用
     *
     * @param userId
     * @param key
     */
    private void setUserIdTokenKeys(long userId, String key) {
        if (0L != userId) {
            String setKey = getUserSessionSetKey(userId);
            try {
                stringRedisTemplate.opsForSet().add(setKey, key);
                stringRedisTemplate.expire(setKey, Constants.Session.SESSION_VALID_TIME, TimeUnit.SECONDS);
            } catch (Exception e) {
                log.error("存储用户注销件失败，userId:{},Exception:{}", userId, e);
            }

        }
    }

    private SessionStruct findSessionBySessionId(String sessionId) {
        String sessionValue = findSessionValueBySessionId(sessionId);
        if (StringUtils.isEmpty(sessionValue)) {
            log.warn("[SessionServiceImpl][findSessionBySessionId] session data 未找到：sid:{}", sessionId);
            return null;
        }
        try {
            SessionValue value = JSON.parseObject(sessionValue, SessionValue.class);
            if (null == value) {
                log.warn("[SessionServiceImpl][findSessionBySessionId] session data 未找到：sid:{},sessionValue:{}", sessionId, sessionValue);
                return null;
            }
            SessionStruct struct = new SessionStruct();
            struct.setSid(sessionId);
            struct.setValues(value);
            return struct;
        } catch (Exception e) {
            log.warn("[SessionServiceImpl][findSessionBySessionId] 序列化SessionValue出错：sid:{},sessionValue:{}", sessionId, sessionValue, e);
            return null;
        }

    }

    @Override
    @CacheEvict(value = "userextinfocache", key = "'extinfo' + #userId", cacheManager = "cacheManager")
    public void deleteByUserId(long userId) {
        //1.删除session关联
        String setKey = getUserSessionSetKey(userId);
        Set useIdKeys = stringRedisTemplate.opsForSet().members(setKey);
        if (!CollectionUtils.isEmpty(useIdKeys)) {
            useIdKeys.forEach(key -> {
                log.info("删除用户userId={}的缓存信息", userId);
                stringRedisTemplate.delete(String.valueOf(key));
            });
            //2.删除session缓存健
            stringRedisTemplate.delete(setKey);
        }
    }

    /**
     * 删除注销后缓存查询结果
     *
     * @param user
     */
    @Caching(evict = {@CacheEvict(value = "usercache", key = "'xyqbuser' + #user.phoneNo", cacheManager = "cacheManager"),
            @CacheEvict(value = "usercache", key = "'xyqbuser' + #user.uuid", cacheManager = "cacheManager"),
            @CacheEvict(value = "addresscache", key = "'address' + #user.id", cacheManager = "cacheManager"),
            @CacheEvict(value = "contact", key = "'contact' + #user.id", cacheManager = "cacheManager"),
            @CacheEvict(value = "userSpouseCache", key = "'spouse' + #user.id", cacheManager = "cacheManager"),
            @CacheEvict(value = "btRegisterCache", key = "'userId' + #user.id", cacheManager = "cacheManager")})
    @Override
    public void deleteUserCatch(User user) {

    }

    @Override
    public List<SessionStruct> findByUserId(long userId) {
        List<SessionStruct> sessionStructList = new ArrayList<>();
        String setKey = getUserSessionSetKey(userId);
        Set<String> userIdKeys = stringRedisTemplate.opsForSet().members(setKey);
        if (CollectionUtils.isEmpty(userIdKeys)) {
            return sessionStructList;
        }
        for (String userIdKey : userIdKeys) {
            String sessionId = stringRedisTemplate.opsForValue().get(userIdKey);
            SessionStruct sessionStruct = findSessionBySessionId(sessionId);
            sessionStructList.add(sessionStruct);
        }
        return sessionStructList;
    }

    @Override
    public void persistSession(List<SessionStruct> sessionStructList) {
        for (SessionStruct sessionStruct : sessionStructList) {
            String sid = sessionStruct.getSid();
            SessionValue values = sessionStruct.getValues();
            persistSession(sid, values);
        }
    }

    @Override
    public void deleteSession(String token) {

        SessionStruct sessionStruct = findSessionBySessionId(token);

        if (null != sessionStruct) {

            stringRedisTemplate.delete(Constants.Session.USER_SESSION_CACHE + sessionStruct.getSid());

            SessionValue values = sessionStruct.getValues();

            User user = values.getUser();

            String key = generateLoginPropertiesKey(user.getId(), values.getLoginProperties());

            stringRedisTemplate.delete(key);

            log.info("登出成功token:{} , user: {}", token, user);
        }
    }

    @Override
    public void persistSessionExchange(String token, SessionValue sessionValue, long expire) {
        Timestamp current = new Timestamp(System.currentTimeMillis());
        sessionValue.setLastAccessTime(current);
        String json = JSON.toJSONString(sessionValue);
        stringRedisTemplate.opsForValue().set(Constants.Session.USER_SESSION_CACHE + token, json,
                Constants.Session.SESSION_VALID_TIME, TimeUnit.SECONDS);
        String key = generateLoginPropertiesKey(sessionValue.getUser().getId(), sessionValue.getLoginProperties());
        stringRedisTemplate.opsForValue().set(key, token, expire, TimeUnit.SECONDS);
        setUserIdTokenKeys(sessionValue.getUser().getId(), key);
    }


    public void kdspDeleteSession(Long userId, LoginProperties loginProperties) {
        List<Long> createFromList = Arrays.asList(214L, 217L);
        for (Long createFrom : createFromList) {
            loginProperties.setCreatedFrom(createFrom);
            String key = generateLoginPropertiesKey(userId, loginProperties);
            String token = stringRedisTemplate.opsForValue().get(key);
            deleteSession(token);
        }
    }



    /**
     * 获取用户的会话缓存Set的Redis-Key
     *
     * @param userId - 用户主键
     * @return
     */
    private String getUserSessionSetKey(long userId) {
        return Constants.Session.USER_SESSION_KEY_SET + userId;
    }
}
