package com.liquidnet.service.sweet.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liquidnet.commons.lang.util.CollectionUtil;
import com.liquidnet.commons.lang.util.CurrentUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.service.adam.dto.vo.AdamAddressesVo;
import com.liquidnet.service.base.PagedResult;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.feign.adam.api.FeignAdamBaseClient;
import com.liquidnet.service.feign.stone.api.FeignStoneIntegralClient;
import com.liquidnet.service.stone.dto.StoneScoreListDto;
import com.liquidnet.service.sweet.dto.vo.IntegralActivityDrawVo;
import com.liquidnet.service.sweet.dto.vo.IntegralActivityVo;
import com.liquidnet.service.sweet.dto.vo.admin.SweetIntegralActivityPrizeVo;
import com.liquidnet.service.sweet.entity.SweetIntegralActivityDraw;
import com.liquidnet.service.sweet.entity.SweetIntegralActivityPrize;
import com.liquidnet.service.sweet.mapper.SweetIntegralActivityDrawMapper;
import com.liquidnet.service.sweet.service.ISweetIntegralActivityDrawService;
import com.liquidnet.service.sweet.utils.QueueUtils;
import com.liquidnet.service.sweet.utils.RedisDataUtils;
import com.liquidnet.service.sweet.utils.SweetNewObjectUtil;
import org.apache.commons.lang.StringUtils;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;

/**
 * <p>
 * 积分活动抽奖记录表 服务实现类
 * </p>
 *
 * @author jiangxiulong
 * @since 2021-10-26
 */
@Service
public class SweetIntegralActivityDrawServiceImpl extends ServiceImpl<SweetIntegralActivityDrawMapper, SweetIntegralActivityDraw> implements ISweetIntegralActivityDrawService {

    @Autowired
    private RedisDataUtils redisDataUtils;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private QueueUtils queueUtils;

    @Autowired
    private FeignStoneIntegralClient feignStoneIntegralClient;
    @Autowired
    private FeignAdamBaseClient feignAdamBaseClient;

    @Override
    public ResponseDto<SweetIntegralActivityPrizeVo> create(String integralActivityId) {
        // TODO: 2021/10/26 看情况是否加锁  一个用户整个逻辑单进程
        Map tokenClaims = CurrentUtil.getTokenClaims();
        String userId = StringUtils.defaultString((String.valueOf(tokenClaims.get("sub"))), "");
        String nickname = StringUtils.defaultString((String.valueOf(tokenClaims.get("nickname"))), "");

        IntegralActivityVo integralActivity = redisDataUtils.getIntegralActivityInfo(integralActivityId);
        if (integralActivity.getIsOnline() == 1) {
            return ResponseDto.failure("该活动已下限");
        }
        if (integralActivity.getTimeType() == 2) {
            LocalDateTime startTime = integralActivity.getStartTime();
            LocalDateTime endTime = integralActivity.getEndTime();
            LocalDateTime nowTime = LocalDateTime.now();
            if (nowTime.isAfter(endTime)) { // 已结束
                return ResponseDto.failure("该活动已结束");
            }
            if (startTime.isAfter(nowTime)) { // 未开始
                return ResponseDto.failure("该活动未开始");
            }
        }

        // 判断用户积分是否>=integralActivity.setActivityNum();
        ResponseDto<StoneScoreListDto> userInfoRes = feignStoneIntegralClient.stoneUserInfo();
        if (!userInfoRes.getCode().equals("0")) {
            return ResponseDto.failure("获取用户积分数据失败");
        } else {
            StoneScoreListDto userInfoData = userInfoRes.getData();
            if (userInfoData.getStatus() == 2) {
                return ResponseDto.failure("您的积分账户已被冻结！");
            } else {
                BigDecimal scoreRest = userInfoData.getScoreRest();
                if (scoreRest.compareTo(BigDecimal.valueOf(integralActivity.getActivityNum())) == -1) {
                    return ResponseDto.failure("您的积分账户余额不足！");
                }
            }
        }

        // 如果库存没了 限制的中奖人数到了就从列表剔出 列表为空就都没有库存了
        List<SweetIntegralActivityPrize> prizeList = integralActivity.getPrizeList();
        ArrayList<SweetIntegralActivityPrize> sweetIntegralActivityPrizeArrayList = SweetNewObjectUtil.getSweetIntegralActivityPrizeArrayList();
        for (SweetIntegralActivityPrize info : prizeList) {
            int integralActivityPrizeNum = redisDataUtils.getIntegralActivityPrizeNum(integralActivityId, info.getPrizeId());
            int integralActivityPrizeWinnersNum = redisDataUtils.getIntegralActivityPrizeWinnersNum(integralActivityId, info.getPrizeId());
            if (integralActivityPrizeNum > 0 && integralActivityPrizeWinnersNum > 0) {
                sweetIntegralActivityPrizeArrayList.add(info);
            }
        }
        if (CollectionUtils.isEmpty(sweetIntegralActivityPrizeArrayList)) {
            return ResponseDto.failure("活动太火爆了，奖品已经没有库存啦～");
        }
        SweetIntegralActivityPrize sweetIntegralActivityPrize = this.lotteryDraw(sweetIntegralActivityPrizeArrayList);

        SweetIntegralActivityPrizeVo sweetIntegralActivityPrizeVo = SweetIntegralActivityPrizeVo.getNew().copy(sweetIntegralActivityPrize);

        // 减库存 顺便判断库存是否够 todo 这块可能不能这么判断 后台增加库存的时候如果是-1会丢库存 实际-1这个并没有发出
        long activityPrizeNum = redisDataUtils.setIntegralActivityPrizeNum(integralActivityId, sweetIntegralActivityPrizeVo.getPrizeId(), 1);
        long activityPrizeWinnersNum = redisDataUtils.setIntegralActivityPrizeWinnersNum(integralActivityId, sweetIntegralActivityPrizeVo.getPrizeId(), 1);
        if (activityPrizeNum < 0 || activityPrizeWinnersNum < 0) {// 库存不够了
            return ResponseDto.failure("活动太火爆了，奖品已经没有库存啦！");
        }
        // 减积分
        feignStoneIntegralClient.de2111(userId, integralActivity.getActivityNum(), integralActivity.getActivityTitle());
        // 抽奖入库
        String drawId = IDGenerator.nextSnowId();
        IntegralActivityDrawVo integralActivityDrawVo = IntegralActivityDrawVo.getNew();
        integralActivityDrawVo.setDrawId(drawId);
        integralActivityDrawVo.setIntegralActivityId(integralActivityId);
        integralActivityDrawVo.setActivityNum(integralActivity.getActivityNum());
        integralActivityDrawVo.setPrizeId(sweetIntegralActivityPrize.getPrizeId());
        integralActivityDrawVo.setPrizeTitle(sweetIntegralActivityPrize.getPrizeTitle());
        integralActivityDrawVo.setPrizeType(sweetIntegralActivityPrize.getPrizeType());
        integralActivityDrawVo.setPrizeTypeNum(sweetIntegralActivityPrize.getPrizeTypeNum());
        integralActivityDrawVo.setRelationId(sweetIntegralActivityPrize.getRelationId());
        integralActivityDrawVo.setUserId(userId);
        integralActivityDrawVo.setNickname(nickname);
        LocalDateTime nowTime = LocalDateTime.now();
        integralActivityDrawVo.setCreatedAt(nowTime);
        Integer receivingStatus = 1;
        Integer prizeType = sweetIntegralActivityPrize.getPrizeType();
        if (prizeType == 2) {// 中奖积分
            // 增加积分
            feignStoneIntegralClient.in2111(userId, sweetIntegralActivityPrize.getPrizeTypeNum(), integralActivity.getActivityTitle());
            // 发放状态
            receivingStatus = 2;
        }
        if (prizeType == 1) { // 谢谢惠顾
            receivingStatus = 3;
        }
        integralActivityDrawVo.setReceivingStatus(receivingStatus);
        // redis
        redisDataUtils.setIntegralActivityDraw(integralActivityDrawVo);
        // mongo
        mongoTemplate.insert(integralActivityDrawVo, IntegralActivityDrawVo.class.getSimpleName());

        // mysql 抽奖记录
        LinkedList<String> sqls = CollectionUtil.linkedListString();
        LinkedList<Object[]> sqlsDataA = CollectionUtil.linkedListObjectArr();
        sqls.add(SqlMapping.get("integral_activity_draw.insert"));
        sqlsDataA.add(new Object[]{
                drawId, integralActivityId, integralActivity.getActivityNum(),
                sweetIntegralActivityPrize.getPrizeId(), sweetIntegralActivityPrize.getPrizeTitle(), sweetIntegralActivityPrize.getPrizeType(),
                sweetIntegralActivityPrize.getPrizeTypeNum(), sweetIntegralActivityPrize.getRelationId(),
                userId, nickname,
                receivingStatus
        });
        queueUtils.sendMsgByRedis(MQConst.SweetQueue.SWEET_INTEGRAL_ACTIVITY_DRAW.getKey(),
                SqlMapping.gets(sqls, sqlsDataA));

        return ResponseDto.success(sweetIntegralActivityPrizeVo);
    }

    @Override
    public ResponseDto<PagedResult<IntegralActivityDrawVo>> drawList(Integer type, String integralActivityId, Integer pageNum) {
        String currentUid = CurrentUtil.getCurrentUid();
        PagedResult<IntegralActivityDrawVo> sweetIntegralActivityDrawPagedResult = SweetNewObjectUtil.getSweetIntegralActivityDrawVoPagedResult();
        long count = 0;
        if (pageNum <= 1) {
            List<IntegralActivityDrawVo> integralActivityDrawAll = redisDataUtils.getIntegralActivityDrawList(type, currentUid, integralActivityId, pageNum);
            count = integralActivityDrawAll.size();
            sweetIntegralActivityDrawPagedResult.setList(integralActivityDrawAll).setTotal(count, 20).setPageSize(20).setCurrentPage(pageNum);
        } else {// page大于1 去mongo里面取
            HashMap<String, Object> info = CollectionUtil.mapStringObject();
            // 排序 分页
            Pageable pageable = PageRequest.of(pageNum - 1, 20, Sort.by(Sort.Direction.DESC, "createdAt"));
            //条件
            Query query = Query.query(
                    Criteria.where("integralActivityId").is(integralActivityId).and("userId").is(currentUid)
            );
            if (type == 1) {
                query.addCriteria(Criteria.where("prizeType").ne(1));
            } else {
                query.addCriteria(Criteria.where("prizeType").is(1));
            }
            count = mongoTemplate.count(query, IntegralActivityDrawVo.class, IntegralActivityDrawVo.class.getSimpleName());
            query.with(pageable);
            List<IntegralActivityDrawVo> integralActivityDrawVos = mongoTemplate.find(query, IntegralActivityDrawVo.class, IntegralActivityDrawVo.class.getSimpleName());
            sweetIntegralActivityDrawPagedResult.setList(integralActivityDrawVos).setTotal(count, 20).setPageSize(20).setCurrentPage(pageNum);
        }
        return ResponseDto.success(sweetIntegralActivityDrawPagedResult);
    }

    @Override
    public ResponseDto<List<IntegralActivityDrawVo>> winPrizeUser(String integralActivityId) {
        List<IntegralActivityDrawVo> integralActivityDrawAll = redisDataUtils.getIntegralActivityDrawAll(integralActivityId);
        return ResponseDto.success(integralActivityDrawAll);
    }

    @Override
    public ResponseDto perfectAddress(String drawId, String receivingAddressesId) {
        // 更新缓存
        IntegralActivityDrawVo drawVo = redisDataUtils.getIntegralActivityDrawInfo(drawId);
        drawVo.setReceivingAddressesId(receivingAddressesId);
        // 获取地址数据
        ResponseDto<AdamAddressesVo> queryAddresses = feignAdamBaseClient.queryAddresses(receivingAddressesId, CurrentUtil.getCurrentUid());
        if (!queryAddresses.getCode().equals("0")) {
            return ResponseDto.failure("获取地址信息失败");
        }
        AdamAddressesVo addressesData = queryAddresses.getData();
        String receivingPhone = addressesData.getPhone();
        String receivingName = addressesData.getName();
        String receivingAddress = addressesData.getAddress();

        drawVo.setReceivingName(receivingName);
        drawVo.setReceivingPhone(receivingPhone);
        drawVo.setReceivingAddress(receivingAddress);
        LocalDateTime nowTime = LocalDateTime.now();
        drawVo.setUpdatedAt(nowTime);
        redisDataUtils.setIntegralActivityDrawInfo(drawId, drawVo);

        mongoTemplate.getCollection(IntegralActivityDrawVo.class.getSimpleName()).updateOne(
                Query.query(Criteria.where("drawId").is(drawId)).getQueryObject(),
                new Document("$set", new Document("receivingAddressesId", receivingAddressesId)
                        .append("receivingName", receivingName)
                        .append("receivingPhone", receivingPhone)
                        .append("receivingAddress", receivingAddress)
                        .append("updatedAt", DateUtil.Formatter.yyyyMMddHHmmss.format(nowTime)))
        );

        // mysql
        LinkedList<String> sqls = CollectionUtil.linkedListString();
        LinkedList<Object[]> sqlsDataA = CollectionUtil.linkedListObjectArr();
        sqls.add(SqlMapping.get("integral_activity_draw.update"));
        sqlsDataA.add(new Object[]{
                receivingAddressesId, receivingName, receivingPhone, receivingAddress, nowTime, drawId
        });
        queueUtils.sendMsgByRedis(MQConst.SweetQueue.SWEET_INTEGRAL_ACTIVITY_DRAW.getKey(),
                SqlMapping.gets(sqls, sqlsDataA));

        return ResponseDto.success();
    }

    @Override
    public ResponseDto<IntegralActivityDrawVo> detail(String drawId) {
        IntegralActivityDrawVo drawVo = redisDataUtils.getIntegralActivityDrawInfo(drawId);
        return ResponseDto.success(drawVo);
    }

    /**
     * 抽奖方法
     */
    private static SweetIntegralActivityPrize lotteryDraw(List<SweetIntegralActivityPrize> prizeList) {
        if (prizeList == null || prizeList.isEmpty()) {
            return null;
        }

        int size = prizeList.size();

        // 计算总概率，这样可以保证不一定总概率是1
        BigDecimal sumRate = BigDecimal.ZERO;
        for (SweetIntegralActivityPrize prize : prizeList) {
            sumRate = sumRate.add(prize.getWinningProbability());
        }

        // 计算每个物品在总概率的基础下的概率情况
        List<BigDecimal> sortOrignalRates = new ArrayList<>(size);
        BigDecimal tempSumRate = BigDecimal.ZERO;
        for (SweetIntegralActivityPrize prize : prizeList) {
            tempSumRate = tempSumRate.add(prize.getWinningProbability());
            sortOrignalRates.add(tempSumRate.divide(sumRate, 4, BigDecimal.ROUND_HALF_UP));
        }

        // 根据区块值来获取抽取到的物品索引
        double nextDouble = Math.random();
        BigDecimal nextDoubleNew = BigDecimal.valueOf(nextDouble);
        sortOrignalRates.add(nextDoubleNew);
        Collections.sort(sortOrignalRates);

        int index = sortOrignalRates.indexOf(nextDoubleNew);

        SweetIntegralActivityPrize sweetIntegralActivityPrize = prizeList.get(index);
        return sweetIntegralActivityPrize;
    }

    /*public static void main(String[] args) {

        List<SweetIntegralActivityPrizeVo> gifts = new ArrayList<>();
        SweetIntegralActivityPrizeVo aNew = SweetIntegralActivityPrizeVo.getNew();
        SweetIntegralActivityPrizeVo aNew2 = SweetIntegralActivityPrizeVo.getNew();
        SweetIntegralActivityPrizeVo aNew3 = SweetIntegralActivityPrizeVo.getNew();
        aNew.setWinningProbability(BigDecimal.valueOf(0.4));
        aNew.setPrizeTitle("一等奖");
        aNew.setPrizeId("111");
        aNew2.setWinningProbability(BigDecimal.valueOf(0.5));
        aNew2.setPrizeTitle("二等奖");
        aNew2.setPrizeId("222");
        aNew3.setWinningProbability(BigDecimal.valueOf(0.1));
        aNew3.setPrizeTitle("三等奖");
        aNew3.setPrizeId("333");
        gifts.add(aNew);
        gifts.add(aNew2);
        gifts.add(aNew3);

        // 统计
        Map<Integer, Integer> count = new HashMap<>();

        // 测试次数
        double num = 1000000;
        for (int i = 0; i < num; i++) {
            SweetIntegralActivityPrizeVo orignalIndex = lottery(gifts);
            Integer integer = count.get(Integer.valueOf(orignalIndex.getPrizeId()));
            count.put(Integer.valueOf(orignalIndex.getPrizeId()), integer == null ? 1 : integer + 1);
        }

        for (Map.Entry<Integer, Integer> entry : count.entrySet()) {
            System.out.println(entry.getKey() + ", 命中次数=" + entry.getValue() + ", 实际概率="
                    + entry.getValue() / num);
        }
    }*/

}
