package com.zanxiang.game.module.sdk.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zanxiang.game.module.base.pojo.enums.BanStatusEnum;
import com.zanxiang.game.module.base.util.DateUtils;
import com.zanxiang.game.module.mybatis.entity.GameExt;
import com.zanxiang.game.module.mybatis.entity.User;
import com.zanxiang.game.module.mybatis.entity.UserToken;
import com.zanxiang.game.module.mybatis.mapper.UserTokenMapper;
import com.zanxiang.game.module.sdk.constant.RedisKeyConstant;
import com.zanxiang.game.module.sdk.enums.ExpireTimeEnum;
import com.zanxiang.game.module.sdk.enums.LoginTypeEnum;
import com.zanxiang.game.module.sdk.enums.TokenCheckEnum;
import com.zanxiang.game.module.sdk.pojo.dto.UserTokenDTO;
import com.zanxiang.game.module.sdk.pojo.param.UserData;
import com.zanxiang.game.module.sdk.service.*;
import com.zanxiang.game.module.sdk.util.RedisUtil;
import com.zanxiang.game.module.sdk.util.SignUtil;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.bean.BeanUtil;
import com.zanxiang.module.util.encryption.Md5Util;
import com.zanxiang.module.util.exception.BaseException;
import com.zanxiang.module.util.pojo.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Objects;
import java.util.UUID;

/**
 * @author : lingfeng
 * @time : 2022-06-07
 * @description : 用户token管理
 */
@Slf4j
@Service
public class UserTokenServiceImpl extends ServiceImpl<UserTokenMapper, UserToken> implements IUserTokenService {

    @Autowired
    private RedisUtil<UserToken> redisUtil;

    @Autowired
    private IGameExtService gameExtService;

    @Autowired
    private IIpBanService ipBanService;

    @Autowired
    private IUserService userService;

    @Autowired
    private IUserLoginLogService userLoginLogService;

    @Override
    public ResultVO<Long> cpTokenCheck(String appId, Long userId, String token, String sign) {
        //查询登录签名
        GameExt gameExt = gameExtService.getByGameAppId(appId);
        if (gameExt == null || Strings.isBlank(gameExt.getLoginKey())) {
            log.error("token验证失败 , 游戏拓展信息不存在, appId : {}, userId : {}", appId, userId);
            return ResultVO.fail(TokenCheckEnum.PARAM_LACK.getMsg());
        }
        //用户信息
        User user = userService.getById(userId);
        //查询token是否存在
        UserToken userToken = super.getOne(new LambdaQueryWrapper<UserToken>()
                .eq(UserToken::getToken, token)
                .eq(UserToken::getUserId, userId));
        //导量用户携带的是 relationUserId 匹配的token
        if (userToken == null && user.getRelationUserId() != null) {
            userToken = super.getOne(new LambdaQueryWrapper<UserToken>()
                    .eq(UserToken::getToken, token)
                    .eq(UserToken::getUserId, user.getRelationUserId()));
        }
        //判断token是否存在, 并且没有过期
        if (userToken == null || userToken.getExpireTime() < DateUtils.localDateTimeToSecond(LocalDateTime.now())) {
            log.error("token验证失败 , token不存在或者已经失效, appId : {}, userId : {}, token : {}", appId, userId, token);
            return ResultVO.fail(TokenCheckEnum.SIGN_ERROR.getMsg());
        }
        //登录密钥
        String loginKey = gameExt.getLoginKey();
        //计算用户签名
        StringBuilder sb = new StringBuilder();
        sb.append("loginKey=").append(loginKey);
        sb.append("&appId=").append(appId);
        sb.append("&userId=").append(userId);
        sb.append("&token=").append(token);
        String mySign;
        try {
            mySign = SignUtil.MD5(sb.toString());
        } catch (Exception e) {
            log.error("md5工具类加密异常, str : {}, e : {}", sb.toString(), e.getMessage());
            throw new BaseException("MD5加密异常");
        }
        //签名错误
        if (!Objects.equals(mySign, sign)) {
            log.error("token验证失败 , str : {}, mySign : {}, sign : {}", sb.toString(), mySign, sign);
            return ResultVO.fail(TokenCheckEnum.CHECK_FAIL.getMsg());
        }
        ResultVO<Long> re = ResultVO.ok(userId);
        log.error("token验证成功, str : {}, mySign : {}, sign : {}, re : {}", sb.toString(), mySign, sign, JsonUtil.toString(re));
        return re;
    }

    @Override
    public Boolean userTokenExpireTimeCheck(UserData userData) {
        log.error("缓存token验证请求 userData : {}", JsonUtil.toString(userData));
        //判断是否存在用户id或者token是否存在
        if (userData.getUserId() == null || Strings.isBlank(userData.getToken())) {
            return Boolean.FALSE;
        }
        //判断token在数据库是否存在
        UserToken userToken = super.getOne(new LambdaQueryWrapper<UserToken>()
                .eq(UserToken::getToken, userData.getToken())
                .eq(UserToken::getDeviceType, userData.getDeviceType()));
        //token已经更新, 返回失效
        if (userToken == null) {
            return Boolean.FALSE;
        }
        //判断是否ip封禁
        if (ipBanService.checkIpBan(userData)) {
            return Boolean.FALSE;
        }
        //查询用户信息
        User user = userService.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getId, userData.getUserId())
                .eq(User::getGameId, userData.getGameId()));
        //判断账号是否存在,或者停用
        if (user == null || Objects.equals(BanStatusEnum.BAN_STATUS.getStatus(), user.getStatus())) {
            return Boolean.FALSE;
        }
        //token检测
        Long expireTime = userData.getExpireTime();
        if (expireTime == null) {
            return Boolean.FALSE;
        }
        //判断token是否过期
        long endTime = DateUtils.localDateTimeToSecond(LocalDateTime.now().plusDays(1));
        //判断有效期时间是否大于1天
        boolean result = expireTime > endTime;
        //token未过期, 记录登录日志
        if (result) {
            userLoginLogService.createUserLoginLog(userData, LoginTypeEnum.LOGIN_IN.getLoginType());
        }
        return result;
    }

    @Override
    public UserTokenDTO getTokenInfoByTokenDevice(String token, Integer deviceType) {
        //查询缓存信息
        String tokenInfoKey = this.getTokenInfoKey(token, deviceType);
        UserToken userToken = redisUtil.getCache(tokenInfoKey);
        if (userToken != null) {
            return BeanUtil.copy(userToken, UserTokenDTO.class);
        }
        //缓存不存在, 查询数据库
        userToken = super.getOne(new LambdaQueryWrapper<UserToken>()
                .eq(UserToken::getToken, token)
                .eq(UserToken::getDeviceType, deviceType));
        if (userToken == null) {
            return null;
        }
        //设置token详情缓存
        redisUtil.setCache(tokenInfoKey, userToken, ExpireTimeEnum.ONE_DAY.getTime());
        //设置用户缓存
        String userTokenKey = this.getUserTokenKey(userToken.getUserId(), deviceType);
        redisUtil.setCache(userTokenKey, userToken, ExpireTimeEnum.ONE_DAY.getTime());
        //返回数据
        return BeanUtil.copy(userToken, UserTokenDTO.class);
    }

    @Override
    public String getUserToken(Long userId, Integer deviceType) {
        //获取token
        String token = this.createToken(userId, deviceType);
        //查询数据库token信息
        UserToken userToken = super.getOne(new LambdaQueryWrapper<UserToken>()
                .eq(UserToken::getUserId, userId)
                .eq(UserToken::getDeviceType, deviceType));
        //不存在token数据, 直接创建
        if (userToken == null) {
            userToken = UserToken.builder()
                    .userId(userId)
                    .token(token)
                    .deviceType(deviceType)
                    .createTime(LocalDateTime.now())
                    .updateTime(LocalDateTime.now())
                    .expireTime(DateUtils.localDateTimeToSecond(LocalDateTime.now()) + ExpireTimeEnum.ONE_WEEK.getTime())
                    .build();
            super.save(userToken);
        } else {
            //更新数据库
            userToken.setToken(token);
            userToken.setUpdateTime(LocalDateTime.now());
            userToken.setExpireTime(DateUtils.localDateTimeToSecond(LocalDateTime.now()) + ExpireTimeEnum.ONE_WEEK.getTime());
            super.updateById(userToken);
        }
        //设置用户缓存
        String userTokenKey = this.getUserTokenKey(userId, deviceType);
        redisUtil.setCache(userTokenKey, userToken, ExpireTimeEnum.ONE_DAY.getTime());
        //设置token详情缓存
        String tokenInfoKey = this.getTokenInfoKey(token, deviceType);
        redisUtil.setCache(tokenInfoKey, userToken, ExpireTimeEnum.ONE_DAY.getTime());
        //返回token
        return token;
    }

    @Override
    public void deleteUserToken(Long userId, Integer deviceType) {
        UserToken userToken = super.getOne(new LambdaQueryWrapper<UserToken>()
                .eq(UserToken::getUserId, userId)
                .eq(UserToken::getDeviceType, deviceType)
        );
        if (userToken == null) {
            return;
        }
        //根据主键删除数据库
        super.removeById(userToken.getId());
        //删除缓存
        String userTokenKey = this.getUserTokenKey(userId, userToken.getDeviceType());
        redisUtil.deleteCache(userTokenKey);
        String tokenInfoKey = this.getTokenInfoKey(userToken.getToken(), userToken.getDeviceType());
        redisUtil.deleteCache(tokenInfoKey);
    }

    private String createToken(Long userId, Integer deviceType) {
        //生成随机数
        String random = userId + deviceType + UUID.randomUUID().toString() + System.currentTimeMillis();
        //创建token
        return Md5Util.encrypt32(Md5Util.encrypt32(random));
    }

    private String getUserTokenKey(Long userId, Integer deviceType) {
        return RedisKeyConstant.USER_TOKEN_KEY + "_" + userId + "_" + deviceType;
    }

    private String getTokenInfoKey(String token, Integer deviceType) {
        return RedisKeyConstant.TOKEN_INFO_KEY + "_" + token + "_" + deviceType;
    }
}
