package com.liquidnet.service.adam.biz;

import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.common.exception.constant.ErrorCode;
import com.liquidnet.commons.lang.util.BeanUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.commons.lang.util.ServletUtils;
import com.liquidnet.commons.lang.util.StringUtil;
import com.liquidnet.service.ResponseDto;
import com.liquidnet.service.adam.constant.AdamComConstants;
import com.liquidnet.service.adam.constant.AdamRedisConstants;
import com.liquidnet.service.adam.constant.AdamUserConstants;
import com.liquidnet.service.adam.constant.AdamUserConstants.UserLoginLogMsgTypeEnum;
import com.liquidnet.service.adam.dto.base.AdamCurrentUser;
import com.liquidnet.service.adam.entity.AdamComInfo;
import com.liquidnet.service.adam.entity.AdamLoginLog;
import com.liquidnet.service.adam.entity.AdamLoginOnline;
import com.liquidnet.service.adam.entity.AdamUser;
import com.liquidnet.service.adam.mapper.AdamComInfoMapper;
import com.liquidnet.service.adam.mapper.AdamUserMapper;
import com.liquidnet.service.adam.service.IAdamLoginLogService;
import com.liquidnet.service.adam.service.IAdamLoginOnlineService;
import com.liquidnet.service.adam.service.IAdamUserPermissionService;
import com.liquidnet.service.adam.util.CurrentUserUtil;
import com.liquidnet.service.api.sequence.feign.FeignSequenceClient;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author AnJiabin <jiabin.an@lightnet.io>
 * @version V1.0
 * @Description: 登录登出相关业务类
 * @class: LoginOutBiz
 * @Package com.liquidnet.service.adam.biz
 * @Copyright: LightNet @ Copyright (c) 2020
 * @date 2020/11/24 10:26 上午
 */
@Slf4j
@Component
@RefreshScope
public class AdamUserLoginBiz {
    @Autowired
    private IAdamLoginOnlineService adamLoginOnlineService;

    @Autowired
    private FeignSequenceClient feignSequenceClient;

    @Autowired
    private IAdamLoginLogService adamLoginLogService;

    @Autowired
    private AdamUserMapper adamUserMapper;

    @Autowired
    private AdamComInfoMapper adamComInfoMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private IAdamUserPermissionService adamUserPermissionService;

    @Value("${liquidnet.system.updating.switch}")
    private String systemUpdatingSwitch;

    @Value("${liquidnet.conf.login.token.expire-time}")
    private int tokenExpireTime;

    @Value("${liquidnet.conf.login.time-out}")
    private int loginTimeOutTime;

    /**
     * 用户登录缓存设置
     */
    public void userLoginCacheSet(String userId, String token, String ip, List<String> permitList) {
        //判断是否在其它site登录
        if (isExitOtherSiteLoginUser(userId, token)) {
            //记录登出日志
            this.insertLoginLog(userId, UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_OTHER_SITE);
        }

        String userLoginKey = this.getUserLoginKey(userId);
        redisUtil.hset(userLoginKey, AdamRedisConstants.USER_LOGIN_ITEM_TOKEN, token, TimeUnit.SECONDS.toSeconds(tokenExpireTime));
        redisUtil.set(this.getUserLoginTimeoutKey(userId), token, TimeUnit.SECONDS.toSeconds(loginTimeOutTime));
        // Function permissions cache
        redisUtil.hset(userLoginKey, AdamRedisConstants.USER_LOGIN_ITEM_PERMIT, permitList);

        //保存用户登录信息
        this.saveLoginOnlineInfo(userId, ip, token);
    }

    /**
     * 保存用户登录信息
     */
    public void saveLoginOnlineInfo(String userId, String ip, String token) {
        final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));

        // 获取客户端操作系统
        String os = userAgent.getOperatingSystem().getName();
        // 获取客户端浏览器
        String browser = userAgent.getBrowser().getName();

        LocalDateTime startTimestamp = DateUtil.asLocalDateTime(new Date());
        AdamLoginOnline adamLoginOnline = new AdamLoginOnline();
        AdamLoginOnline oldBean = adamLoginOnlineService.selectOnlineByUserId(userId);
        if (StringUtil.isNotNull(oldBean)) {
            adamLoginOnline = oldBean;
            adamLoginOnline.setSessionId(token);
            adamLoginOnline.setIpaddr(ip);
            adamLoginOnline.setLoginLocation(ip);
            adamLoginOnline.setBrowser(browser);
            adamLoginOnline.setOs(os);
            adamLoginOnline.setStatus(ErrorCode.SUCCESS.getCode());
            adamLoginOnline.setStartTimestamp(startTimestamp);
            adamLoginOnline.setLastAccessTime(DateUtil.asLocalDateTime(new Date()));
            adamLoginOnline.setExpireTime(Integer.parseInt(String.valueOf(this.getLoginTimeOutTime())));
        } else {
            adamLoginOnline.setId(feignSequenceClient.nextId().toString());
            adamLoginOnline.setUserId(userId);
            adamLoginOnline.setSessionId(token);
            adamLoginOnline.setComId("");
            adamLoginOnline.setAccountNo("");
            adamLoginOnline.setTelephone("");
            adamLoginOnline.setIpaddr(ip);
            adamLoginOnline.setLoginLocation(ip);
            adamLoginOnline.setBrowser(browser);
            adamLoginOnline.setOs(os);
            adamLoginOnline.setStatus(ErrorCode.SUCCESS.getCode());
            adamLoginOnline.setStartTimestamp(startTimestamp);
            adamLoginOnline.setLastAccessTime(DateUtil.asLocalDateTime(new Date()));
            adamLoginOnline.setExpireTime(Integer.parseInt(String.valueOf(this.getLoginTimeOutTime())));
        }

        adamLoginOnlineService.saveOrUpdate(adamLoginOnline);

        //记录登出日志
        this.insertLoginLog(userId, UserLoginLogMsgTypeEnum.LOGIN_SUCCESS);
    }

    /**
     * 用户主动退出
     */
    public void userLogout(String userId) {
        this.forceLogout(userId, UserLoginLogMsgTypeEnum.LOGIN_OUT_SUCCESS);
    }

    /**
     * 系统强退用户
     */
    public void forceLogout(String userId, UserLoginLogMsgTypeEnum userLoginLogMsgTypeEnum) {
        //记录登出日志
        this.insertLoginLog(userId, userLoginLogMsgTypeEnum);
        //删除登录信息
        adamLoginOnlineService.deleteOnlineByUserId(userId);
        //清除登录缓存
        this.removeUserCache(userId);
    }

    /**
     * 清理用户缓存
     *
     * @param userId 登录名称
     */
    public void removeUserCache(String userId) {
        Object tokenObj = redisUtil.hget(this.getUserLoginKey(userId), AdamRedisConstants.USER_LOGIN_ITEM_TOKEN);
        if (null == tokenObj) {
            return;
        }
        String token = String.valueOf(tokenObj);
        redisUtil.del(this.getUserLoginKey(userId));
        log.info("[removeUserTokenCache]; param userId : {}, token : {}", this.getUserLoginKey(userId), token);
        redisUtil.del(this.getUserLoginTimeoutKey(userId));
        log.info("[removeUserLoginTimeOutCache]; param userId : {}, token : {}", this.getUserLoginTimeoutKey(userId), token);
    }

    /**
     * 刷新用户登录token
     */
    public void refreshUserTokenExpireTime(String userId, String token) {
        //默认不需要刷新用户信息
        this.refreshUserTokenExpireTime(userId, token, false);
    }

    /**
     * 刷新用户登录token
     */
    public void refreshUserTokenExpireTime(String userId, String token, boolean isUpdateUserInfo) {
        redisUtil.hset(this.getUserLoginKey(userId), AdamRedisConstants.USER_LOGIN_ITEM_TOKEN, token, this.getTokenExpireTime());

        AdamCurrentUser currentUser = CurrentUserUtil.getCurrentUser(ServletUtils.getRequest());

        if (currentUser == null || isUpdateUserInfo) {
            currentUser = new AdamCurrentUser();
            AdamUser adamUser = adamUserMapper.selectById(userId);
            BeanUtils.copyProperties(adamUser, currentUser);
        }

        CurrentUserUtil.setCurrentUser(ServletUtils.getRequest(), currentUser);
    }

    /**
     * 当邮件更新时设置邮件更新标志
     */
    public void refreshUserCacheByEmailChange(String userId) {
        Object userTokenObj = redisUtil.get(this.getUserLoginKey(userId));
        if (userTokenObj != null) {
            redisUtil.hset(this.getUserLoginKey(userId), AdamRedisConstants.USER_LOGIN_TOKEN_ITEM_EMAIL_CHANGE, true);
        }
    }

    /**
     * login out
     */
    public ResponseDto<Object> loginOutCheck(String userId, String newToken) {
        ResponseDto<Object> returnDto = new ResponseDto<>();

        //判断系统是否正在维护
        if (this.systemIsMaintenance(userId)) {
            returnDto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            returnDto.setData(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_SYS_UPDATE.name());
            returnDto.setMessage(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_SYS_UPDATE.getDesc());
            return returnDto;
        }
        //判断email是否已经修改
        if (this.emailIsUpdated(userId)) {
            returnDto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            returnDto.setData(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_EMAIL_UPDATE.name());
            returnDto.setMessage(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_EMAIL_UPDATE.getDesc());
            return returnDto;
        }
        //判断用户登录是否超时
        if (this.isOptionTimeout(userId)) {
            returnDto.setCode(ErrorCode.LOGIN_TIME_OUT.getCode());
            returnDto.setData(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_LOGIN_TIMEOUT.name());
            returnDto.setMessage(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_LOGIN_TIMEOUT.getDesc());
            return returnDto;
        }
        //是否已登录其他site
        if (this.isExitOtherSiteLoginUser(userId, newToken)) {
            returnDto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            returnDto.setData(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_OTHER_SITE.name());
            returnDto.setMessage(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_OTHER_SITE.getDesc());
            return returnDto;
        }
        //当前用户是否有效
        if (!this.userIsActive(userId)) {
            returnDto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            returnDto.setData(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_USER_FROZEN.name());
            returnDto.setMessage(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_USER_FROZEN.getDesc());
            return returnDto;
        }
        //判断公司是否有效
        if (!this.companyProfileIsAvailable(userId)) {
            returnDto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            returnDto.setData(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_COM_FROZEN.name());
            returnDto.setMessage(UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_COM_FROZEN.getDesc());
            return returnDto;
        }

        return ResponseDto.success(UserLoginLogMsgTypeEnum.LOGIN_SUCCESS.getDesc());
    }

    //判断该用户是否已经有登陆的site，有则强制退出，然后登陆新site
    private boolean isExitOtherSiteLoginUser(String userId, String token) {
        //判断是否存在已登录token
        Object oldTokenObj = redisUtil.hget(this.getUserLoginKey(userId), AdamRedisConstants.USER_LOGIN_ITEM_TOKEN);
        if (StringUtil.isNotNull(oldTokenObj)) {
            String oldToken = String.valueOf(oldTokenObj);
            if (!oldToken.equalsIgnoreCase(token)) {
                return true;
            }
        }
        return false;
    }

    //判断用户是否还存在，且状态正常
    private boolean userIsActive(String userId) {
        AdamUser adamUser = adamUserMapper.selectById(userId);
        if (adamUser != null) {
            if (!AdamUserConstants.UserStatusEnum.ACTIVE.getCode().equalsIgnoreCase(adamUser.getStatus())) {
                return false;
            }
        }
        return true;
    }

    //判断用户的公司资料是否有效
    private boolean companyProfileIsAvailable(String userId) {
        AdamCurrentUser currentUser = CurrentUserUtil.getCurrentUser(ServletUtils.getRequest());
        if (currentUser != null && StringUtil.isNotEmpty(currentUser.getComId())) {
            AdamComInfo adamComInfo = adamComInfoMapper.selectById(currentUser.getComId());
            if (!adamComInfo.getState().equalsIgnoreCase(AdamComConstants.ComStateEnum.S8.getCode())) {
                return false;
            }
        }
        return true;
    }

    //判断系统是否正在维护中
    private boolean systemIsMaintenance(String userId) {
        log.info("AdamUserLoginBiz systemIsMaintenance : {} ", systemUpdatingSwitch);
        if (StringUtil.isNotEmpty(systemUpdatingSwitch))
            return Boolean.parseBoolean(systemUpdatingSwitch);
        return false;
    }

    //判断用户是否累计使用系统超过90分钟
    private boolean isOptionTimeout(String userId) {
        Object tokenObj = redisUtil.get(this.getUserLoginTimeoutKey(userId));
        if (StringUtil.isEmpty(tokenObj)) {
            return true;
        } else {
            return false;
        }
    }

    //判断电子邮件是否已经修改为最新
    private boolean emailIsUpdated(String userId) {
        Object tokenObj = redisUtil.hget(this.getUserLoginKey(userId), AdamRedisConstants.USER_LOGIN_TOKEN_ITEM_EMAIL_CHANGE);
        if (StringUtil.isNotNull(tokenObj)) {
            return (boolean) tokenObj;
        }
        return false;
    }

    /**
     * 组装userToken使用key
     */
    public String getUserLoginKey(String userId) {
        return AdamRedisConstants.USER_LOGIN_KEY + userId;
    }

    /**
     * 组装userLoginTimeKey使用key
     */
    private String getUserLoginTimeoutKey(String userId) {
        return AdamRedisConstants.USER_LOGIN_TIME_OUT_KEY + userId;
    }

    /**
     * 获取token超时时间
     */
    private long getTokenExpireTime() {
        return TimeUnit.SECONDS.toSeconds(tokenExpireTime);
    }

    /**
     * 获取用户登录超时时间
     */
    private long getLoginTimeOutTime() {
        return TimeUnit.SECONDS.toSeconds(loginTimeOutTime);
    }

    /**
     * 插入login日志
     */
    private void insertLoginLog(String userId, UserLoginLogMsgTypeEnum loginLogMsgTypeEnum) {
        AdamLoginOnline adamLoginOnline = adamLoginOnlineService.selectOnlineByUserId(userId);
        if (StringUtil.isNotNull(adamLoginOnline)) {
            this.insertLoginLog(adamLoginOnline, loginLogMsgTypeEnum);
        }
    }

    /**
     * 插入登录日志
     */
    private void insertLoginLog(AdamLoginOnline adamLoginOnline, UserLoginLogMsgTypeEnum loginLogMsgTypeEnum) {
        AdamLoginLog log = new AdamLoginLog();
        BeanUtil.copy(adamLoginOnline, log);
        log.setId(feignSequenceClient.nextId());
        log.setStatus(loginLogMsgTypeEnum.getStatus());
        log.setMsg(loginLogMsgTypeEnum.getDesc());
        log.setLoginTime(adamLoginOnline.getStartTimestamp());
        adamLoginLogService.save(log);
    }

}
