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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.zanxiang.game.module.base.pojo.enums.BanStatusEnum;
import com.zanxiang.game.module.base.pojo.enums.GameCategoryEnum;
import com.zanxiang.game.module.base.pojo.enums.HttpStatusEnum;
import com.zanxiang.game.module.mybatis.entity.*;
import com.zanxiang.game.module.sdk.constant.RedisKeyConstant;
import com.zanxiang.game.module.sdk.enums.KafkaEventTrackEnum;
import com.zanxiang.game.module.sdk.enums.LoginTypeEnum;
import com.zanxiang.game.module.sdk.enums.SmsTypeEnum;
import com.zanxiang.game.module.sdk.pojo.dto.GameAppletDTO;
import com.zanxiang.game.module.sdk.pojo.param.LoginMobileParam;
import com.zanxiang.game.module.sdk.pojo.param.LoginPasswordParam;
import com.zanxiang.game.module.sdk.pojo.param.LoginVxCodeParam;
import com.zanxiang.game.module.sdk.pojo.param.UserData;
import com.zanxiang.game.module.sdk.pojo.vo.UserLoginVO;
import com.zanxiang.game.module.sdk.service.*;
import com.zanxiang.game.module.sdk.service.api.WxApiService;
import com.zanxiang.game.module.sdk.util.RedisUtil;
import com.zanxiang.game.module.sdk.util.RegexUtil;
import com.zanxiang.game.module.sdk.util.RegisterUtil;
import com.zanxiang.module.redis.service.IDistributedLockComponent;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.exception.BaseException;
import com.zanxiang.module.util.pojo.ResultVO;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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 reactor.util.function.Tuple2;
import reactor.util.function.Tuple3;
import reactor.util.function.Tuples;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author : lingfeng
 * @time : 2022-06-22
 * @description : 注册登录逻辑
 */
@Slf4j
@Service
public class LoginServiceImpl implements IRegisterLoginService {

    @Autowired
    private RedisUtil<String> redisUtil;

    @Autowired
    private IUserTokenService userTokenService;

    @Autowired
    private IUserLoginLogService userLoginLogService;

    @Autowired
    private IUserService userService;

    @Autowired
    private ISmsService smsService;

    @Autowired
    private IWordCheckService wordCheckService;

    @Autowired
    private IUserCardService userCardService;

    @Autowired
    private IGameUserService gameUserService;

    @Autowired
    private IGameExtService gameExtService;

    @Autowired
    private IIpBanService ipBanService;

    @Autowired
    private ICallBackService callBackService;

    @Autowired
    private IGameAppletService gameAppletService;

    @Autowired
    private WxApiService wxApiService;

    @Autowired
    private IUserShareService userShareService;

    @Autowired
    private IAgentService agentService;

    @Autowired
    private IKafkaService kafkaService;

    @Autowired
    private IGameService gameService;

    @Autowired
    private IGameAppletShellService gameAppletShellService;

    @Autowired
    private IUserAgentLogService userAgentLogService;

    @Autowired
    private IGameUserRoleService gameUserRoleService;

    @Autowired
    private IDistributedLockComponent distributedLockComponent;

    @Autowired
    private IUserEventService userEventService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultVO<UserLoginVO> loginWxCode(LoginVxCodeParam param, UserData userData, HttpServletRequest request) {
        log.error("登录请求参数 , param : {}, userData : {}", JsonUtil.toString(param), JsonUtil.toString(userData));
        //验证是否被封禁
        Tuple2<Boolean, HttpStatusEnum> banCheckTuple2 = ipBanService.banCheck(userData);
        if (banCheckTuple2.getT1()) {
            return ResultVO.fail(banCheckTuple2.getT2().getMsg());
        }
        //查询微信小游戏信息
        GameAppletDTO gameAppletDTO = gameAppletService.getByGameId(userData.getGameId());
        //获取用户微信小游戏的openId
        Map<String, String> resultMap = wxApiService.getOpenIdByType(param.getCode(), gameAppletDTO.getAppId(),
                gameAppletDTO.getAppSecret(), gameAppletDTO.getType());
        String openId = resultMap.get("openid");
        String sessionKey = resultMap.get("session_key");
        //根据openId查询用户
        User user = userService.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getGameId, userData.getGameId()).eq(User::getOpenId, openId));
        //已注册
        if (user != null) {
            //账号停用, 返回登录失败
            if (Objects.equals(BanStatusEnum.BAN_STATUS.getStatus(), user.getStatus())) {
                return ResultVO.fail(HttpStatusEnum.ACCOUNT_HALT.getMsg());
            }
            //更新用户sessionKey
            if (Strings.isNotBlank(sessionKey)) {
                userService.update(new LambdaUpdateWrapper<User>()
                        .set(User::getSessionKey, sessionKey).eq(User::getId, user.getId()));
            }
            //渠道更新和回传判断
            agentService.userAgentUpdate(user, userData);
            //返回登录信息
            UserLoginVO userLoginVO = this.createUserLoginVO(user, userData, Boolean.FALSE);
            userLoginVO.setAppletShellSwitch(gameAppletShellService.getUserShellSwitch(user, Boolean.FALSE, request));
            userLoginVO.setAccessToken(wxApiService.getAccessToken(gameAppletDTO.getAppId(), gameAppletDTO.getAppSecret()));
            userLoginVO.setSessionKey(sessionKey);
            log.error("返回用户登录信息, userLoginVO : {}", JsonUtil.toString(userLoginVO));
            return ResultVO.ok(userLoginVO);
        }
        //用户注册
        user = userCreateSave(userData, openId, null, null, openId, sessionKey);
        //返回登录信息
        UserLoginVO userLoginVO = this.createUserLoginVO(user, userData, Boolean.TRUE);
        userLoginVO.setAppletShellSwitch(gameAppletShellService.getUserShellSwitch(user, Boolean.TRUE, request));
        userLoginVO.setAccessToken(wxApiService.getAccessToken(gameAppletDTO.getAppId(), gameAppletDTO.getAppSecret()));
        userLoginVO.setSessionKey(sessionKey);
        log.error("返回用户登录信息, userLoginVO : {}", JsonUtil.toString(userLoginVO));
        return ResultVO.ok(userLoginVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultVO<UserLoginVO> loginPassword(LoginPasswordParam param, UserData userData) {
        //验证是否被封禁
        Tuple2<Boolean, HttpStatusEnum> banCheckTuple2 = ipBanService.banCheck(userData);
        if (banCheckTuple2.getT1()) {
            return ResultVO.fail(banCheckTuple2.getT2().getMsg());
        }
        String username = param.getUsername();
        String password = param.getPassword();
        //用户信息
        User user;
        log.error("登录参数 param : {}, userData : {}", JsonUtil.toString(param), JsonUtil.toString(userData));
        //登录, 进行登录检测
        if (Objects.equals(param.getType(), LoginPasswordParam.LOGIN)) {
            //判断是否手机号
            if (RegexUtil.checkPhone(username)) {
                log.error("手机登录, username : {}", username);
                user = userService.getOne(new LambdaQueryWrapper<User>()
                        .eq(User::getGameId, userData.getGameId()).eq(User::getMobile, username));
            } else {
                //根据用户名查询
                log.error("账号登录, username : {}", username);
                user = userService.getOne(new LambdaQueryWrapper<User>()
                        .eq(User::getGameId, userData.getGameId()).eq(User::getUsername, username));
            }
            //用户信息不存在
            if (user == null) {
                return ResultVO.fail(HttpStatusEnum.USERNAME_OR_PASSWORD_ERR.getMsg());
            }
            //判断账号是否停用
            if (Objects.equals(BanStatusEnum.BAN_STATUS.getStatus(), user.getStatus())) {
                return ResultVO.fail(HttpStatusEnum.ACCOUNT_HALT.getMsg());
            }
            //验证密码
            if (!Objects.equals(RegisterUtil.cmfPassword(password), user.getPassword())) {
                return ResultVO.fail(HttpStatusEnum.USERNAME_OR_PASSWORD_ERR.getMsg());
            }
            //渠道更新和回传判断
            agentService.userAgentUpdate(user, userData);
            //登录信息
            UserLoginVO userLoginVO = this.createUserLoginVO(user, userData, Boolean.FALSE);
            Tuple2<Boolean, Long> tuple2 = this.userGuideCheck(user);
            //导量用户返回原有的用户id
            if (tuple2.getT1()) {
                userLoginVO.setUserId(tuple2.getT2());
            }
            //返回登录信息
            return ResultVO.ok(userLoginVO);
        }
        //设备账号数量检测
        this.accountCheck(username, userData);
        //用户注册, 用户名密码校验
        HttpStatusEnum checkRegisterEnum = this.checkRegister(userData.getGameId(), username, password);
        if (!Objects.equals(checkRegisterEnum, HttpStatusEnum.SUCCESS)) {
            return ResultVO.fail(checkRegisterEnum.getMsg());
        }
        //用户注册
        user = userCreateSave(userData, username, RegisterUtil.cmfPassword(password), null, null, null);
        //返回登录信息
        UserLoginVO userLoginVO = this.createUserLoginVO(user, userData, Boolean.TRUE);
        return ResultVO.ok(userLoginVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultVO<UserLoginVO> loginMobile(LoginMobileParam param, UserData userData) {
        //验证是否被封禁
        Tuple2<Boolean, HttpStatusEnum> banCheckTuple2 = ipBanService.banCheck(userData);
        if (banCheckTuple2.getT1()) {
            return ResultVO.fail(banCheckTuple2.getT2().getMsg());
        }
        String mobile = param.getMobile();
        //验证码校验
        HttpStatusEnum httpStatusEnum = smsService.smsCheck(SmsTypeEnum.SMS_REG.getType(), mobile, param.getCode());
        //验证不通过, 返回
        if (!Objects.equals(httpStatusEnum, HttpStatusEnum.SUCCESS)) {
            return ResultVO.fail(httpStatusEnum.getMsg());
        }
        //获取用户信息
        User user = userService.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getGameId, userData.getGameId()).eq(User::getMobile, mobile));
        //用户信息存在
        if (user != null) {
            //判断账号是否停用
            if (Objects.equals(BanStatusEnum.BAN_STATUS.getStatus(), user.getStatus())) {
                return ResultVO.fail(HttpStatusEnum.ACCOUNT_HALT.getMsg());
            }
            //渠道更新和回传判断
            agentService.userAgentUpdate(user, userData);
            UserLoginVO userLoginVO = this.createUserLoginVO(user, userData, Boolean.FALSE);
            //判断是否导量用户
            Tuple2<Boolean, Long> tuple2 = this.userGuideCheck(user);
            //导量用户返回原有的用户id
            if (tuple2.getT1()) {
                userLoginVO.setUserId(tuple2.getT2());
                log.error("导量用户返回用户信息, userLoginVO : {}", JsonUtil.toString(userLoginVO));
            }
            //返回登录信息
            return ResultVO.ok(userLoginVO);
        }
        //设备账号数量检测
        this.accountCheck(mobile, userData);
        //用户注册
        user = userCreateSave(userData, mobile, null, mobile, null, null);
        //返回登录信息
        UserLoginVO userLoginVO = this.createUserLoginVO(user, userData, Boolean.TRUE);
        return ResultVO.ok(userLoginVO);
    }

    private void accountCheck(String userName, UserData userData) {
        //只限制仙剑导量包
        if (!Objects.equals(userData.getGameId(), 36L)) {
            return;
        }
        //非安卓设备, 无法限制
        if (Strings.isBlank(userData.getAndroidId())) {
            return;
        }
        //内部账号, 不受监控
        if (userEventService.count(new LambdaQueryWrapper<UserEvent>()
                .eq(UserEvent::getGameId, userData.getGameId())
                .and(qw -> qw.eq(UserEvent::getUsername, userName)
                        .or().eq(UserEvent::getMobile, userName)
                        .or().eq(UserEvent::getIp, userData.getIp()))
        ) > 0) {
            return;
        }
        //判断安卓设备已经注册过2个账号
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
                .eq(User::getGameId, userData.getGameId());
        //条件匹配
        queryWrapper.and(qw -> {
            boolean hasCondition = false;
            //安卓id匹配
            if (StringUtils.isNoneBlank(userData.getAndroidId())) {
                qw.or().eq(User::getAndroidId, userData.getAndroidId());
                hasCondition = true;
            }
            //oaid匹配
            if (StringUtils.isNoneBlank(userData.getOaid())) {
                qw.or().eq(User::getOaid, userData.getOaid());
                hasCondition = true;
            }
            //imei匹配
            if (StringUtils.isNoneBlank(userData.getImei())) {
                qw.or().eq(User::getImei, userData.getImei());
                hasCondition = true;
            }
            // 避免无条件的OR查询
            if (!hasCondition) {
                qw.or().apply("1=0");
            }
        });
        if (userService.count(queryWrapper) >= 2) {
            throw new BaseException("注册异常，请勿重复申请注册账号！");
        }
    }

    private Tuple2<Boolean, Long> userGuideCheck(User user) {
        //非导量用户
        if (user.getRelationUserId() == null) {
            return Tuples.of(Boolean.FALSE, 0L);
        }
        //导量用户信息
        User relationUser = userService.getById(user.getRelationUserId());
        //确定导量匹配
        if (relationUser != null) {
            try {
                //监测角色是否导全, 或者导过之后又跑去小程序创建了其他角色
                gameUserRoleService.userGuideGameRoleCheck(user, relationUser);
            } catch (Exception ignored) {
            }
            return Tuples.of(Boolean.TRUE, relationUser.getId());
        }
        //非导量兜底逻辑
        return Tuples.of(Boolean.FALSE, 0L);
    }

    private User userCreateSave(UserData userData, String userName, String password, String mobile, String openId, String sessionKey) {
        //获取游戏信息
        Game game = gameService.getById(userData.getGameId());
        //判断游戏是否禁止注册
        if (Objects.equals(game.getRegisterCloseSwitch(), Boolean.TRUE)) {
            throw new BaseException("注册异常，请联系客服!");
        }
        //锁Key
        String lockKey = RedisKeyConstant.USER_CREATE + userData.getGameId() + "_" + userName;
        //上锁
        if (!distributedLockComponent.doLock(lockKey, 0L, 3L, TimeUnit.MINUTES)) {
            throw new BaseException("用户信息正在注册中, 请勿重复请求!");
        }
        //渠道id, 链接参数, 分享人id
        Tuple3<Long, Map<String, String>, String> tuple3 = agentService.getUserAgentId(game, userData, openId);
        //分享人id
        Long shareUserId = Strings.isBlank(tuple3.getT3()) ? null : Long.valueOf(tuple3.getT3());
        //用户参数补充
        User user = transform(userData, tuple3.getT1(), userName);
        user.setPassword(password);
        user.setMobile(mobile);
        user.setOpenId(Strings.isNotBlank(openId) ? openId : tuple3.getT2().get("openId"));
        user.setSessionKey(sessionKey);
        user.setShareUserId(shareUserId);
        //App的渠道保存解析后的参数, 因为涉及到时间判断
        if (Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_APP.getId())) {
            user.setChannel(JsonUtil.toString(tuple3.getT2()));
        } else {
            user.setChannel(userData.getChannel());
        }
        userService.save(user);
        gameUserService.createGameUser(user);
        userAgentLogService.regAgentLog(user);
        //非分享用户, 回传注册
        if (shareUserId == null) {
            callBackService.userCallBack(user, tuple3.getT2());
        } else {
            //分享用户,记录分享信息, 用户不回传
            userShareService.createShareLog(user, userData);
        }
        //注册信息埋点数据发送到卡夫卡
        kafkaService.eventTrack(KafkaEventTrackEnum.KAFKA_EVENT_TRACK_REG, JsonUtil.toString(user));
        //发送到IP解析任务队列
        redisUtil.addToSet(RedisKeyConstant.IP_DATA_ASSAY_QUEUE, "REG:" + user.getId() + ":" + user.getIp());
        //返回用户信息
        return user;
    }

    private User transform(UserData userData, Long agentId, String userName) {
        return User.builder()
                .regAgentId(agentId)
                .agentId(agentId)
                .gameId(userData.getGameId())
                .username(userName)
                .nickname(RegisterUtil.randomNickName(userName))
                .deviceType(userData.getDeviceType())
                .status(BanStatusEnum.NORMAL_STATUS.getStatus())
                .authentication(0)
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .deviceSystem(userData.getDeviceSystem())
                .mac(userData.getMac())
                .imei(userData.getImei())
                .oaid(userData.getOaid())
                .androidId(userData.getAndroidId())
                .idfa(userData.getIdfa())
                .caid(userData.getCaid())
                .ip(userData.getIp())
                .build();
    }

    private UserLoginVO createUserLoginVO(User user, UserData userData, Boolean regUser) {
        //注册, 补全 userData 中的用户id
        if (userData.getUserId() == null) {
            userData.setUserId(user.getId());
        } else {
            //登录, 发送登录埋点数据到卡夫卡
            kafkaService.eventTrack(KafkaEventTrackEnum.KAFKA_EVENT_TRACK_LOGIN, JsonUtil.toString(user));
        }
        //获取token
        String userToken = userTokenService.getUserToken(user.getId(), userData.getDeviceType());
        //插入用户登录记录
        userLoginLogService.createUserLoginLog(userData, regUser ? LoginTypeEnum.LOGIN_REG : LoginTypeEnum.LOGIN_IN);
        //查询用户实名信息
        UserCard userCard = userCardService.getOne(new LambdaQueryWrapper<UserCard>()
                .eq(UserCard::getUserId, user.getId())
                .orderByDesc(UserCard::getCreateTime)
                .last("limit 1"));
        //查询游戏
        GameExt gameExt = gameExtService.getByGameId(userData.getGameId());
        //构造用户登录信息
        UserLoginVO userLoginVO = UserLoginVO.builder()
                .userId(user.getId())
                .openId(user.getOpenId())
                .userName(user.getUsername())
                .token(userToken)
                .authentication(user.getAuthentication())
                .bindPhone(Strings.isBlank(user.getMobile()) ? 0 : 1)
                .hasOldPassword(Strings.isNotBlank(user.getPassword()))
                .phoneNum(user.getShowPhoneNum())
                .cardName(userCard == null ? null : userCard.getShowCardName())
                .cardId(userCard == null ? null : userCard.getShowCardId())
                .checkSwitch(gameExt.getCheckSwitch())
                .regUser(regUser)
                .build();
        log.error("用户登录返回登录信息, userLoginVO : {}", JsonUtil.toString(userLoginVO));
        return userLoginVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean loginOut(UserData userData) {
        //新增用户退出记录
        return userLoginLogService.createUserLoginLog(userData, LoginTypeEnum.LOGIN_OUT);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean writeOff(UserData userData) {
        //删除token
        userTokenService.deleteUserToken(userData.getUserId(), userData.getDeviceType());
        return Boolean.TRUE;
    }

    private HttpStatusEnum checkRegister(Long gameId, String username, String password) {
        //判断用户名是否存在敏感词
        if (wordCheckService.hasWord(username)) {
            return HttpStatusEnum.USERNAME_SENSITIVE;
        }
        //判断用户名是否已存在
        if (userService.count(new LambdaQueryWrapper<User>()
                .eq(User::getGameId, gameId)
                .eq(User::getUsername, username)
        ) > 0) {
            return HttpStatusEnum.USERNAME_EXISTS;
        }
        //用户名合规检测
        HttpStatusEnum checkUserNameEnum = RegisterUtil.checkUserName(username);
        if (!Objects.equals(checkUserNameEnum, HttpStatusEnum.SUCCESS)) {
            return checkUserNameEnum;
        }
        //密码验证
        return RegisterUtil.checkPassword(password);
    }
}
