package com.skr.mdm.authentication.user;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.skr.mdm.authentication.TokenExpireException;
import com.skr.mdm.dao.DeviceEquipmentDao;
import com.skr.mdm.dto.LoginUserDTO;
import com.skr.mdm.dto.TokenDTO;
import com.skr.mdm.entity.DeviceEquipmentDO;
import com.skr.mdm.enums.ResultCodeEnum;
import com.skr.mdm.util.JwtUserUtil;
import com.skr.mdm.util.RedisGetKeyUtil;
import com.skr.mdm.util.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * @author nfq
 * @date 2020/7/16 9:34
 */
@Service
@Slf4j
public class UserTokenHandlerImpl implements UserTokenHandler {

    @Value("${token.expire.seconds}")
    private Long tokenExpireSeconds;
    @Value("${token.expire.refreshSeconds}")
    private Long refreshTokenExpireSeconds;


    @Autowired
    private DeviceEquipmentDao equipmentDao;
    @Autowired
    private RedisUtils redisUtils;

    @Override
    public TokenDTO createToken(LoginUserDTO loginUserDTO) {
        delLoginUser(loginUserDTO.getId());

        loginUserDTO.setLoginTime(System.currentTimeMillis());
        loginUserDTO.setLogonTime(new Date());
        // 生成token
        String token = IdUtil.simpleUUID();
        loginUserDTO.setToken(token);
        loginUserDTO.setTokenExpireTime(loginUserDTO.getLoginTime() + tokenExpireSeconds * 1000);
        String refreshToken = IdUtil.simpleUUID();
        loginUserDTO.setRefreshToken(refreshToken);
        loginUserDTO.setRefreshTokenExpireTime(loginUserDTO.getLoginTime() + refreshTokenExpireSeconds * 1000);
        // 保存token
        LambdaUpdateChainWrapper<DeviceEquipmentDO> lambdaUpdateWrapper = new  LambdaUpdateChainWrapper<>(equipmentDao);
        lambdaUpdateWrapper.set(DeviceEquipmentDO::getToken, token).set(DeviceEquipmentDO::getRefreshToken, refreshToken)
                .set(DeviceEquipmentDO::getLogonTime, new Date())
                .eq(DeviceEquipmentDO::getId, loginUserDTO.getId()).update();

        // 加入缓存
        redisUtils.set(RedisGetKeyUtil.getUserTokenKey(token), loginUserDTO, tokenExpireSeconds);
        redisUtils.set(RedisGetKeyUtil.getUserRefreshTokenKey(refreshToken), loginUserDTO, refreshTokenExpireSeconds);
        redisUtils.set(RedisGetKeyUtil.getUserIdKey(loginUserDTO.getId()), loginUserDTO, refreshTokenExpireSeconds);

        String jwt = JwtUserUtil.getJwt(loginUserDTO);

        TokenDTO tokenDTO = TokenDTO.builder().access_token(jwt).refresh_token(refreshToken)
                .expirationTime(loginUserDTO.getTokenExpireTime()).loginTime(loginUserDTO.getLoginTime()).build();

        return tokenDTO;
    }

    @Override
    public LoginUserDTO getLoginUserByJwt(String jwt) throws TokenExpireException {
        String token = JwtUserUtil.getTokenByJwt(jwt);
        if (StringUtils.isBlank(token)) {
            // token不合法
            throw new TokenExpireException(ResultCodeEnum.TOKEN_ILLEGAL.getCode(), ResultCodeEnum.TOKEN_ILLEGAL.getMsg());
        }
        LoginUserDTO loginUserDTO = (LoginUserDTO) redisUtils.get(RedisGetKeyUtil.getUserTokenKey(token));
        if (loginUserDTO == null) {
            LambdaQueryChainWrapper<DeviceEquipmentDO> queryChainWrapper = new LambdaQueryChainWrapper<>(equipmentDao);
            DeviceEquipmentDO equipmentDo = queryChainWrapper.eq(DeviceEquipmentDO::getToken, token).one();
            if (equipmentDo == null) {
                throw new TokenExpireException(ResultCodeEnum.TOKEN_EXPIRE.getCode(), ResultCodeEnum.TOKEN_EXPIRE.getMsg());
            }
            LoginUserDTO loginUser = new LoginUserDTO();
            BeanUtils.copyProperties(equipmentDo, loginUser);
            loginUser.setLoginTime(loginUser.getLogonTime().getTime());
            loginUser.setTokenExpireTime(loginUser.getLogonTime().getTime() + tokenExpireSeconds * 1000);
            loginUser.setRefreshTokenExpireTime(loginUser.getLogonTime().getTime() + refreshTokenExpireSeconds * 1000);
            redisUtils.set(RedisGetKeyUtil.getUserTokenKey(token), loginUser, tokenExpireSeconds);
            return loginUser;
        }
        return loginUserDTO;
    }

    @Override
    public TokenDTO refreshToken(String jwt_token, String refresh_token) {

        LoginUserDTO loginUserDTO = (LoginUserDTO) redisUtils.get(RedisGetKeyUtil.getUserRefreshTokenKey(refresh_token));
        if (loginUserDTO == null) {
            // 数据库获取
            LoginUserDTO loginUser = getLoginUserByRefreshToken(refresh_token);
            if (loginUser == null) {
                return null;
            }
            delLoginUser(loginUser.getId());
            return createNewToken(loginUser, jwt_token, refresh_token);
        }
        delLoginUser(loginUserDTO.getId());
        return createNewToken(loginUserDTO, jwt_token, refresh_token);
    }

    @Override
    public void emptyRedisToken(String jwt) {
        if (StringUtils.isBlank(jwt)) {
            return;
        }
        try {
            String token = JwtUserUtil.getTokenByJwt(jwt);
            if (redisUtils.get(RedisGetKeyUtil.getUserTokenKey(token)) == null) {
                return;
            }
            redisUtils.del(RedisGetKeyUtil.getUserTokenKey(token));
        } catch (TokenExpireException e) {
//           log.warn("用户jwt已过期");
        }
    }

    @Override
    public void signOut(String jwt) throws TokenExpireException {
        if (StringUtils.isBlank(jwt)) {
            return;
        }
        String token = JwtUserUtil.getTokenByJwt(jwt);
        if (redisUtils.get(RedisGetKeyUtil.getUserTokenKey(token)) == null) {
            return;
        }
        LoginUserDTO loginUserDTO = (LoginUserDTO) redisUtils.get(RedisGetKeyUtil.getUserTokenKey(token));
        redisUtils.del(RedisGetKeyUtil.getUserTokenKey(token));
        redisUtils.del(RedisGetKeyUtil.getUserRefreshTokenKey(loginUserDTO.getRefreshToken()));
        redisUtils.del(RedisGetKeyUtil.getUserIdKey(loginUserDTO.getId()));
        // 清空数据库token和refreshToken
        LambdaUpdateChainWrapper<DeviceEquipmentDO> updateChainWrapper = new LambdaUpdateChainWrapper<>(equipmentDao);
        updateChainWrapper.set(DeviceEquipmentDO::getToken, null).set(DeviceEquipmentDO::getRefreshToken, null)
                .set(DeviceEquipmentDO::getOfflineTime, new Date()).eq(DeviceEquipmentDO::getToken, token).update();
    }

    @Override
    public LoginUserDTO getLoginUserByRefreshToken(String refreshToken) {
        LambdaQueryChainWrapper<DeviceEquipmentDO> queryChainWrapper = new LambdaQueryChainWrapper<>(equipmentDao);
        DeviceEquipmentDO equipmentDo = queryChainWrapper.eq(DeviceEquipmentDO::getRefreshToken, refreshToken).one();
        if (equipmentDo == null) {
            return null;
        }
        long refreshTokenExpire = equipmentDo.getLogonTime().getTime() + refreshTokenExpireSeconds * 1000;
        if (refreshTokenExpire > System.currentTimeMillis()) {
            LoginUserDTO loginUserDTO = new LoginUserDTO();
            BeanUtils.copyProperties(equipmentDo, loginUserDTO);
            loginUserDTO.setLoginTime(equipmentDo.getLogonTime().getTime());
            loginUserDTO.setRefreshToken(refreshToken);
            loginUserDTO.setRefreshTokenExpireTime(refreshTokenExpire);
            return loginUserDTO;
        }
        return null;
    }


    private TokenDTO createNewToken(LoginUserDTO loginUserDTO, String jwt_token, String refresh_token) {
        // 生成新的token和refresh_token（刷新token过期时间不变）
        String token = IdUtil.simpleUUID();
        loginUserDTO.setToken(token);
        loginUserDTO.setTokenExpireTime(System.currentTimeMillis() + tokenExpireSeconds * 1000);
        String refreshToken = IdUtil.simpleUUID();
        loginUserDTO.setRefreshToken(refreshToken);

        LambdaUpdateChainWrapper<DeviceEquipmentDO> chainWrapper = new LambdaUpdateChainWrapper<DeviceEquipmentDO>(equipmentDao);
        chainWrapper.set(DeviceEquipmentDO::getToken, token).set(DeviceEquipmentDO::getRefreshToken, refreshToken)
                .eq(DeviceEquipmentDO::getId, loginUserDTO.getId()).update();

        redisUtils.set(RedisGetKeyUtil.getUserTokenKey(token), loginUserDTO, tokenExpireSeconds);
        Long refreshTokenExp = (loginUserDTO.getRefreshTokenExpireTime() - System.currentTimeMillis()) / 1000;
        redisUtils.set(RedisGetKeyUtil.getUserRefreshTokenKey(refreshToken), loginUserDTO, refreshTokenExp);
        // 清除缓存
        emptyRedisToken(jwt_token);
        redisUtils.del(RedisGetKeyUtil.getUserRefreshTokenKey(refresh_token));

        String jwtToken = JwtUserUtil.getJwt(loginUserDTO);
        return new TokenDTO(jwtToken, refreshToken, loginUserDTO.getLoginTime(), loginUserDTO.getTokenExpireTime());
    }

    /**
     * 清除用户设备相关缓存
     * @param userId
     */
    private void delLoginUser(Integer userId) {
        if (redisUtils.get(RedisGetKeyUtil.getUserIdKey(userId)) != null) {
            LoginUserDTO loginUser = (LoginUserDTO) redisUtils.get(RedisGetKeyUtil.getUserIdKey(userId));
            redisUtils.del(RedisGetKeyUtil.getUserTokenKey(loginUser.getToken()));
            redisUtils.del(RedisGetKeyUtil.getUserRefreshTokenKey(loginUser.getRefreshToken()));
            redisUtils.del(RedisGetKeyUtil.getUserIdKey(userId));
        }
    }
}
