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

import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.sd4324530.jtuple.Tuple2;
import com.zanxiang.game.back.base.pojo.enums.OrderStatusEnum;
import com.zanxiang.game.module.manage.constant.RedisKeyConstant;
import com.zanxiang.game.module.manage.pojo.dto.GameGiftPackConditionDTO;
import com.zanxiang.game.module.manage.pojo.dto.UserDTO;
import com.zanxiang.game.module.manage.pojo.params.GameGiftPackCodeLogListParam;
import com.zanxiang.game.module.manage.pojo.vo.GameGiftPackCodeLogVO;
import com.zanxiang.game.module.manage.service.*;
import com.zanxiang.game.module.manage.utils.RedisUtil;
import com.zanxiang.game.module.mybatis.entity.*;
import com.zanxiang.game.module.mybatis.mapper.GameGiftPackCodeLogMapper;
import com.zanxiang.module.redis.service.IDistributedLockComponent;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.bean.BeanUtil;
import com.zanxiang.module.util.exception.BaseException;
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.support.TransactionTemplate;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author : lingfeng
 * @time : 2022-06-22
 * @description : 游戏礼包码领取日志
 */
@Slf4j
@Service
public class GameGiftPackCodeLogServiceImpl extends ServiceImpl<GameGiftPackCodeLogMapper, GameGiftPackCodeLog> implements IGameGiftPackCodeLogService {

    @Autowired
    private ISmsService smsService;

    @Autowired
    private IUserService userService;

    @Autowired
    private IOrderService orderService;

    @Autowired
    private RedisUtil<String> redisUtil;

    @Autowired
    private IRoleOperateService roleOperateService;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private IUserLoginLogService userLoginLogService;

    @Autowired
    private IGameGiftPackCodeService gameGiftPackCodeService;

    @Autowired
    private IGameGiftPackLinkService gameGiftPackLinkService;

    @Autowired
    private IDistributedLockComponent distributedLockComponent;

    @Autowired
    private IGameGiftPackLinkLogService gameGiftPackLinkLogService;

    @Override
    public IPage<GameGiftPackCodeLogVO> list(GameGiftPackCodeLogListParam param) {
        IPage<GameGiftPackCodeLog> codeLogIPage = page(param.toPage(), new QueryWrapper<GameGiftPackCodeLog>().lambda()
                .eq(GameGiftPackCodeLog::getLinkId, param.getLinkId())
                .ge(param.getCreateBeginTime() != null, GameGiftPackCodeLog::getCreateTime, param.getCreateBeginTime() == null ? null : LocalDateTime.of(param.getCreateBeginTime(), LocalTime.MIN))
                .le(param.getCreateEndTime() != null, GameGiftPackCodeLog::getCreateTime, param.getCreateEndTime() == null ? null : LocalDateTime.of(param.getCreateEndTime(), LocalTime.MAX))
                .orderByDesc(GameGiftPackCodeLog::getCreateTime));
        IPage<GameGiftPackCodeLogVO> result = new Page<>(codeLogIPage.getCurrent(), codeLogIPage.getSize(), codeLogIPage.getTotal());
        if (CollectionUtils.isNotEmpty(codeLogIPage.getRecords())) {
            List<GameGiftPackCodeLog> records = codeLogIPage.getRecords();
            Map<String, String> gameServerMap = gameGiftPackLinkLogService.getSourceGameServerByLinkId(param.getLinkId());
            result.setRecords(this.toVOBatch(records, gameServerMap));
        }
        return result;
    }

    private List<GameGiftPackCodeLogVO> toVOBatch(List<GameGiftPackCodeLog> codeLogList, Map<String, String> gameServerMap) {
        return codeLogList.stream().map(codeLog -> {
            GameGiftPackCodeLogVO gameGiftPackCodeLogVO = BeanUtil.copy(codeLog, GameGiftPackCodeLogVO.class);
            String userPhone = codeLog.getUserPhone();
            String phoneNum = userPhone.substring(0, 3) + " **** " + userPhone.substring(userPhone.length() - 4);
            gameGiftPackCodeLogVO.setUserPhone(phoneNum);
            gameGiftPackCodeLogVO.setServerName(gameServerMap.get(codeLog.getServerId()));
            return gameGiftPackCodeLogVO;
        }).collect(Collectors.toList());
    }

    @Override
    public Map<String, Object> getGiftPackCode(Long linkLogId, String userPhone, String randomCode) {
        //链接访问日志判断
        GameGiftPackLinkLog gameGiftPackLinkLog = gameGiftPackLinkLogService.getById(linkLogId);
        Assert.notNull(gameGiftPackLinkLog, "参数错误, 链接访问日志信息不存在");
        //判断日志参数验证是通过的
        String userId = gameGiftPackLinkLog.getUserId() == null ? null : gameGiftPackLinkLog.getUserId().toString();
        Assert.state(StringUtils.isNoneBlank(userId, gameGiftPackLinkLog.getServerId(), gameGiftPackLinkLog.getRoleId(),
                gameGiftPackLinkLog.getRoleName()), "参数错误, 链接访问记录信息缺失");
        //判断手机号是否属于隐藏手机号
        String phoneNum = userPhone.contains("****") ? gameGiftPackLinkLog.getUserPhone() : userPhone;
        //线程锁key
        String lockKey = RedisKeyConstant.GAME_GIFT_PACK_LOCK + phoneNum;
        //线程锁 3 分钟, 防止同一用户重复请求
        Assert.state(distributedLockComponent.doLock(lockKey, 0L, 3L, TimeUnit.MINUTES), "操作频繁, 请稍后重试");
        //执行礼包码获取
        try {
            Map<String, Object> resuktMap = new HashMap<>(2);
            Tuple2<Boolean, String> tuple2 = this.getRandomCode(randomCode, phoneNum, gameGiftPackLinkLog);
            resuktMap.put("result", tuple2.first);
            resuktMap.put("msg", tuple2.second);
            return resuktMap;
        } finally {
            //执行释放锁动作
            distributedLockComponent.unlock(lockKey);
        }
    }

    private Tuple2<Boolean, String> getRandomCode(String randomCode, String userPhone, GameGiftPackLinkLog linkLog) {
        //手机验证码校验
        smsService.randomCodeCheck(userPhone, randomCode);
        //异步更新角色操作表
        roleOperateService.roleOperateUpdate(userPhone, linkLog);
        //查询记录, 判断是否重复领取, 重复领取直接返回之前领取的礼包码
        Tuple2<Boolean, String> repeatCheck = this.repeatCheck(linkLog);
        if (repeatCheck.first) {
            return Tuple2.with(Boolean.TRUE, repeatCheck.second);
        }
        //构造记录
        GameGiftPackCodeLog gameGiftPackCodeLog = this.transform(linkLog, userPhone);
        //判断礼包码是否存在, 缓存不存在, 或者长度为零, 没有礼包码可领取
        String codeKey = RedisKeyConstant.GAME_GIFT_PACK_CODE + linkLog.getLinkId();
        if (!redisUtil.hasKey(codeKey) || redisUtil.getSetSize(codeKey) <= 0) {
            String msg = "该礼包已被领取完, 请联系客服小姐姐领取其他礼包";
            gameGiftPackCodeLog.setMsg(msg);
            super.save(gameGiftPackCodeLog);
            return Tuple2.with(Boolean.FALSE, msg);
        }
        //判断是否满足领取条件
        Tuple2<Boolean, String> conditionCheck = this.conditionCheck(linkLog);
        //设置判定消息
        gameGiftPackCodeLog.setMsg(conditionCheck.second);
        //判定不通过, 返回错误消息提示
        if (!conditionCheck.first) {
            super.save(gameGiftPackCodeLog);
            return Tuple2.with(Boolean.FALSE, "角色不满足该礼包领取条件, 请联系客服领取其他礼包");
        }
        //从缓存中获取一个礼包码id, 如果缓存中不存在则是领完了
        String codeId = redisUtil.popOfSet(codeKey);
        if (Strings.isBlank(codeId)) {
            String msg = "该礼包已被领取完, 请联系客服小姐姐领取其他礼包";
            gameGiftPackCodeLog.setMsg(msg);
            super.save(gameGiftPackCodeLog);
            return Tuple2.with(Boolean.FALSE, msg);
        }
        //查询礼包码具体信息, 礼包码必须存在, 且未被领取, 否则数据错误
        GameGiftPackCode gameGiftPackCode = gameGiftPackCodeService.getById(Long.valueOf(codeId));
        Assert.notNull(gameGiftPackCode, "礼包码数据错误, 请联系客服小姐姐处理");
        //成功领取礼包码记录数据更新
        try {
            this.codeSendUpdate(gameGiftPackCode, linkLog, gameGiftPackCodeLog);
        } catch (Exception e) {
            //出现数据更新异常, 礼包码放回去
            redisUtil.addToSet(codeKey);
            log.error("数据库表同步更新异常, e: {}", e.getMessage(), e);
            throw new BaseException("礼包码数据更新异常, 请联系客服小姐姐处理");
        }
        //返回礼包码
        return Tuple2.with(Boolean.TRUE, gameGiftPackCode.getCode());
    }

    private Tuple2<Boolean, String> repeatCheck(GameGiftPackLinkLog gameGiftPackLinkLog) {
        GameGiftPackCodeLog giftPackCodeLog = super.getOne(new LambdaQueryWrapper<GameGiftPackCodeLog>()
                .eq(GameGiftPackCodeLog::getLinkId, gameGiftPackLinkLog.getLinkId())
                .eq(GameGiftPackCodeLog::getGameId, gameGiftPackLinkLog.getGameId())
                .eq(GameGiftPackCodeLog::getRoleId, gameGiftPackLinkLog.getRoleId())
                .isNotNull(GameGiftPackCodeLog::getCode));
        return Tuple2.with(giftPackCodeLog != null, giftPackCodeLog != null ? giftPackCodeLog.getCode() : null);
    }

    private void codeSendUpdate(GameGiftPackCode gameGiftPackCode, GameGiftPackLinkLog gameGiftPackLinkLog,
                                GameGiftPackCodeLog gameGiftPackCodeLog) {
        //在同一个事物中进行表更新
        transactionTemplate.execute(status -> {
            //礼包码领取状态修改保存
            gameGiftPackCode.setIsSend(Boolean.TRUE);
            gameGiftPackCodeService.updateById(gameGiftPackCode);
            //领取记录修改保存
            gameGiftPackCodeLog.setCode(gameGiftPackCode.getCode());
            super.save(gameGiftPackCodeLog);
            //更新链接访问日志关联id
            gameGiftPackLinkLog.setCodeLogId(gameGiftPackCodeLog.getId());
            gameGiftPackLinkLog.setUpdateTime(LocalDateTime.now());
            gameGiftPackLinkLogService.updateById(gameGiftPackLinkLog);
            return Boolean.TRUE;
        });
    }

    private GameGiftPackCodeLog transform(GameGiftPackLinkLog gameGiftPackLinkLog, String userPhone) {
        return GameGiftPackCodeLog.builder()
                .linkId(gameGiftPackLinkLog.getLinkId())
                .userPhone(userPhone)
                .gameId(gameGiftPackLinkLog.getGameId())
                .userId(gameGiftPackLinkLog.getUserId())
                .serverId(gameGiftPackLinkLog.getServerId())
                .roleId(gameGiftPackLinkLog.getRoleId())
                .roleName(gameGiftPackLinkLog.getRoleName())
                .corpId(gameGiftPackLinkLog.getCorpId())
                .corpUserId(gameGiftPackLinkLog.getCorpUserId())
                .externalUserId(gameGiftPackLinkLog.getExternalUserId())
                .linkLogId(gameGiftPackLinkLog.getId())
                .createTime(LocalDateTime.now())
                .build();
    }

    private Tuple2<Boolean, String> conditionCheck(GameGiftPackLinkLog gameGiftPackLinkLog) {
        GameGiftPackLink giftPackLink = gameGiftPackLinkService.getById(gameGiftPackLinkLog.getLinkId());
        Assert.notNull(giftPackLink, "参数错误, 礼包链接信息不存在");
        String codeCheck = giftPackLink.getCodeCheck();
        if (Strings.isBlank(codeCheck)) {
            return Tuple2.with(Boolean.TRUE, "领取成功, 礼包码领取条件 - 不存在");
        }
        GameGiftPackConditionDTO conditionDTO = JsonUtil.toObj(codeCheck, GameGiftPackConditionDTO.class);
        //当前时间
        LocalDateTime nowTime = LocalDateTime.now();
        //注册时间区间判断
        if (!this.regTimeCheck(nowTime, gameGiftPackLinkLog, conditionDTO.getRegTime())) {
            return Tuple2.with(Boolean.FALSE, "注册时间区间 - 判定不通过");
        }
        //未登录时间区间判断
        if (!this.unLoginCheck(nowTime, gameGiftPackLinkLog, conditionDTO.getUnLogin())) {
            return Tuple2.with(Boolean.FALSE, "未登录时间区间 - 判定不通过");
        }
        //付费区间判断
        if (!this.payCheck(gameGiftPackLinkLog, conditionDTO.getPay())) {
            return Tuple2.with(Boolean.FALSE, "付费区间判断 - 判定不通过");
        }
        //判定通过
        return Tuple2.with(Boolean.TRUE, "领取成功, 礼包码领取条件 - 全部判定通过");
    }

    private boolean regTimeCheck(LocalDateTime nowTime, GameGiftPackLinkLog gameGiftPackLinkLog,
                                 GameGiftPackConditionDTO.RangeBean regTimeBean) {
        //缺少条件参数, 无法判定, 直接返回通过
        if (regTimeBean == null || regTimeBean.getMin() == null || regTimeBean.getMax() == null) {
            return true;
        }
        //查询玩家信息注册时间
        UserDTO userDTO = userService.getById(gameGiftPackLinkLog.getUserId());
        //玩家信息为空, 无法判定是否满足条件, 返回不通过
        if (userDTO == null || userDTO.getCreateTime() == null) {
            return false;
        }
        //玩家注册时间
        LocalDateTime userRegTime = userDTO.getCreateTime();
        //计算时间区间
        LocalDateTime startTime = nowTime.minusMinutes(regTimeBean.getMin());
        LocalDateTime endTime = nowTime.plusMinutes(regTimeBean.getMax());
        //返回判定
        return userRegTime.isAfter(startTime) && userRegTime.isBefore(endTime);
    }

    private boolean unLoginCheck(LocalDateTime nowTime, GameGiftPackLinkLog gameGiftPackLinkLog,
                                 GameGiftPackConditionDTO.RangeBean unLoginBean) {
        //缺少条件参数, 直接返回通过
        if (unLoginBean == null || unLoginBean.getMin() == null || unLoginBean.getMax() == null) {
            return true;
        }
        //查询玩家最后登录时间
        UserLoginLog userLoginLog = userLoginLogService.getOne(new LambdaQueryWrapper<UserLoginLog>()
                .eq(UserLoginLog::getUserId, gameGiftPackLinkLog.getUserId())
                .eq(UserLoginLog::getGameId, gameGiftPackLinkLog.getGameId())
                .eq(UserLoginLog::getRoleId, gameGiftPackLinkLog.getRoleId())
                .orderByDesc(UserLoginLog::getCreateTime)
                .last("limit 1"));
        //登录记录为空, 无法判定是否满足条件, 返回不通过
        if (userLoginLog == null || userLoginLog.getCreateTime() == null) {
            return false;
        }
        //最后登录时间
        LocalDateTime lastLoginTime = userLoginLog.getCreateTime();
        //计算时间区间
        LocalDateTime startTime = nowTime.minusMinutes(unLoginBean.getMin());
        LocalDateTime endTime = nowTime.plusMinutes(unLoginBean.getMax());
        //返回判定
        return lastLoginTime.isBefore(startTime) || lastLoginTime.isAfter(endTime);
    }

    private boolean payCheck(GameGiftPackLinkLog gameGiftPackLinkLog, GameGiftPackConditionDTO.RangeBean payBean) {
        //缺少条件参数, 无法判定, 直接返回通过
        if (payBean == null || payBean.getMin() == null || payBean.getMax() == null) {
            return true;
        }
        //查询玩家支付成功的订单信息
        List<Order> userOrderList = orderService.list(new LambdaQueryWrapper<Order>()
                .select(Order::getRealAmount)
                .eq(Order::getUserId, gameGiftPackLinkLog.getUserId())
                .eq(Order::getGameId, gameGiftPackLinkLog.getGameId())
                .eq(Order::getRoleId, gameGiftPackLinkLog.getRoleId())
                .eq(Order::getStatus, OrderStatusEnum.SUCCESS_PAY.getValue()));
        //充值订单为空, 判断付费区间最小值是否包含 0, 如果包含0, 则通过, 否则不通过
        if (CollectionUtils.isEmpty(userOrderList)) {
            return Objects.equals(payBean.getMin(), 0L);
        }
        //玩家充值总金额
        BigDecimal userPaySum = userOrderList.stream().map(Order::getRealAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        //返回判定
        return userPaySum.compareTo(BigDecimal.valueOf(payBean.getMin())) >= 0
                && userPaySum.compareTo(BigDecimal.valueOf(payBean.getMax())) <= 0;
    }
}