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.IntegralActivityPrizeVo;
import com.liquidnet.service.sweet.dto.vo.IntegralActivityVo;
import com.liquidnet.service.sweet.entity.SweetIntegralActivityDraw;
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 lombok.extern.slf4j.Slf4j;
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
 */
@Slf4j
@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<IntegralActivityPrizeVo> create(String integralActivityId) {
        Map tokenClaims = CurrentUtil.getTokenClaims();
        String userId = StringUtils.defaultString((String.valueOf(tokenClaims.get("sub"))), "");
        // 加锁
        boolean isLock = redisDataUtils.setIntegralDrawLock(userId);
        if (!isLock) {
            return ResponseDto.failure("您的手速太快啦～");
        } else {
            try {
                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")) {
                    log.error("获取用户积分数据失败 userInfoRes:{}", userInfoRes);
                    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<IntegralActivityPrizeVo> prizeList = integralActivity.getPrizeList();
                ArrayList<IntegralActivityPrizeVo> activityPrizeVoArrayList = SweetNewObjectUtil.getIntegralActivityPrizeVoArrayList();
                for (IntegralActivityPrizeVo info : prizeList) {
                    int integralActivityPrizeNum = redisDataUtils.getIntegralActivityPrizeNum(integralActivityId, info.getPrizeId());
                    if (integralActivityPrizeNum > 0) {
                        activityPrizeVoArrayList.add(info);
                    }
                }
                if (CollectionUtils.isEmpty(activityPrizeVoArrayList)) {
                    return ResponseDto.failure("活动太火爆了，奖品已经没有库存啦～");
                }
                IntegralActivityPrizeVo integralActivityPrizeVo = this.lotteryDraw(activityPrizeVoArrayList);

                // 减库存 顺便判断库存是否够 -1的时候回滚回去
                long activityPrizeNum = redisDataUtils.decrIntegralActivityPrizeNum(integralActivityId, integralActivityPrizeVo.getPrizeId(), 1);
                log.info("奖品库存情况 activityPrizeNum:{}", activityPrizeNum);
                if (activityPrizeNum < 0) {// 库存不够了
                    long prizeNum = redisDataUtils.incrIntegralActivityPrizeNum(integralActivityId, integralActivityPrizeVo.getPrizeId(), 1);
                    log.info("奖品库存情况回滚后 prizeNum:{}", prizeNum);
                    return ResponseDto.failure("活动太火爆了，奖品已经没有库存了！");
                }
                // 减积分
                ResponseDto<String> de2111 = feignStoneIntegralClient.de2111(userId, integralActivity.getActivityNum(), integralActivity.getActivityTitle());
                log.info("减积分返回值情况 de2111:{}", de2111);
                // 抽奖入库
                String drawId = IDGenerator.nextSnowId();
                IntegralActivityDrawVo integralActivityDrawVo = IntegralActivityDrawVo.getNew();
                integralActivityDrawVo.setDrawId(drawId);
                integralActivityDrawVo.setIntegralActivityId(integralActivityId);
                integralActivityDrawVo.setActivityNum(integralActivity.getActivityNum());
                integralActivityDrawVo.setPrizeId(integralActivityPrizeVo.getPrizeId());
                integralActivityDrawVo.setPrizeTitle(integralActivityPrizeVo.getPrizeTitle());
                integralActivityDrawVo.setPrizeType(integralActivityPrizeVo.getPrizeType());
                integralActivityDrawVo.setPrizeTypeNum(integralActivityPrizeVo.getPrizeTypeNum());
//                integralActivityDrawVo.setRelationId(sweetIntegralActivityPrize.getRelationId());
                integralActivityDrawVo.setUserId(userId);
                integralActivityDrawVo.setNickname(nickname);
                LocalDateTime nowTime = LocalDateTime.now();
                integralActivityDrawVo.setCreatedAt(nowTime);
                Integer receivingStatus = 1;
                Integer prizeType = integralActivityPrizeVo.getPrizeType();
                if (prizeType == 2) {// 中奖积分
                    // 增加积分
                    ResponseDto<String> in2111 = feignStoneIntegralClient.in2112(userId, integralActivityPrizeVo.getPrizeTypeNum(), integralActivity.getActivityTitle());
                    log.info("中奖加积分返回值情况 in2111:{}", in2111);
                    // 发放状态
                    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(),
                        integralActivityPrizeVo.getPrizeId(), integralActivityPrizeVo.getPrizeTitle(), integralActivityPrizeVo.getPrizeType(),
                        integralActivityPrizeVo.getPrizeTypeNum(), //sweetIntegralActivityPrize.getRelationId(),
                        userId, nickname,
                        receivingStatus
                });
                queueUtils.sendMsgByRedis(MQConst.SweetQueue.SWEET_INTEGRAL_ACTIVITY_DRAW.getKey(),
                        SqlMapping.gets(sqls, sqlsDataA));

                return ResponseDto.success(integralActivityPrizeVo);
            } catch (Exception e) {
                log.error("积分抽奖异常 e:{}", e);
                return ResponseDto.failure("操作失败，请稍后重试！");
            } finally {
                // 执行完毕。释放锁
                redisDataUtils.delIntegralDrawLock(userId);
            }
        }
    }

    @Override
    public ResponseDto<PagedResult<IntegralActivityDrawVo>> drawList(Integer type, String integralActivityId, Integer pageNum) {
        Integer pageSize = 20;
        String currentUid = CurrentUtil.getCurrentUid();
        PagedResult<IntegralActivityDrawVo> sweetIntegralActivityDrawPagedResult = SweetNewObjectUtil.getSweetIntegralActivityDrawVoPagedResult();
        long count = 0;
        //条件
        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));
        }
        if (pageNum <= 1) {
            LinkedList<IntegralActivityDrawVo> integralActivityDrawList = redisDataUtils.getIntegralActivityDrawList(type, currentUid, integralActivityId);
            count = mongoTemplate.count(query, IntegralActivityDrawVo.class, IntegralActivityDrawVo.class.getSimpleName());
            sweetIntegralActivityDrawPagedResult.setList(integralActivityDrawList).setTotal(count, pageSize).setPageSize(pageSize).setCurrentPage(pageNum);
        } else {// page大于1 去mongo里面取
            HashMap<String, Object> info = CollectionUtil.mapStringObject();
            // 排序 分页
            Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"));
            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, pageSize).setPageSize(pageSize).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, drawVo.getUserId());
        if (!queryAddresses.getCode().equals("0")) {
            return ResponseDto.failure("获取地址信息失败");
        }
        AdamAddressesVo addressesData = queryAddresses.getData();
        String receivingPhone = addressesData.getPhone();
        String receivingName = addressesData.getName();
        String receivingProvince = addressesData.getProvince();
        String receivingCity = addressesData.getCity();
        String receivingCounty = addressesData.getCounty();
        String receivingAddress = addressesData.getAddress();

        drawVo.setReceivingName(receivingName);
        drawVo.setReceivingPhone(receivingPhone);
        drawVo.setReceivingProvince(receivingProvince);
        drawVo.setReceivingCity(receivingCity);
        drawVo.setReceivingCounty(receivingCounty);
        drawVo.setReceivingAddress(receivingAddress);
        drawVo.setReceivingAddressesId(receivingAddressesId);
        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("receivingProvince", receivingProvince)
                        .append("receivingCity", receivingCity)
                        .append("receivingCounty", receivingCounty)
                        .append("receivingAddress", receivingAddress)
                        .append("updatedAt", DateUtil.Formatter.yyyyMMddHHmmss.format(nowTime)))
        );

        LinkedList<IntegralActivityDrawVo> integralActivityDrawList = redisDataUtils.getIntegralActivityDrawList(1, drawVo.getUserId(), drawVo.getIntegralActivityId());
        for (IntegralActivityDrawVo info : integralActivityDrawList) {
            if (info.getDrawId().equals(drawId)) {
                info.setReceivingAddressesId(receivingAddressesId);
                info.setReceivingName(receivingName);
                info.setReceivingPhone(receivingPhone);
                info.setReceivingProvince(receivingProvince);
                info.setReceivingCity(receivingCity);
                info.setReceivingCounty(receivingCounty);
                info.setReceivingAddress(receivingAddress);
                info.setUpdatedAt(nowTime);
            }
        }
        redisDataUtils.setIntegralActivityDrawInList(drawVo.getUserId(), drawVo.getIntegralActivityId(), integralActivityDrawList);

        LinkedList<IntegralActivityDrawVo> integralActivityDrawList2 = redisDataUtils.getIntegralActivityDrawList(2, drawVo.getUserId(), drawVo.getIntegralActivityId());
        for (IntegralActivityDrawVo info : integralActivityDrawList2) {
            if (info.getDrawId().equals(drawId)) {
                info.setReceivingAddressesId(receivingAddressesId);
                info.setReceivingName(receivingName);
                info.setReceivingPhone(receivingPhone);
                info.setReceivingProvince(receivingProvince);
                info.setReceivingCity(receivingCity);
                info.setReceivingCounty(receivingCounty);
                info.setReceivingAddress(receivingAddress);
                info.setUpdatedAt(nowTime);
            }
        }
        redisDataUtils.setIntegralActivityDrawList(drawVo.getUserId(), drawVo.getIntegralActivityId(), integralActivityDrawList2);

        // 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, receivingProvince, receivingCity, receivingCounty, 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);
        IntegralActivityVo integralActivityInfo = redisDataUtils.getIntegralActivityInfo(drawVo.getIntegralActivityId());
        drawVo.setExpressFeeType(integralActivityInfo.getExpressFeeType());
        return ResponseDto.success(drawVo);
    }

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

        int size = prizeList.size();

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

        // 计算每个物品在总概率的基础下的概率情况
        List<BigDecimal> sortOrignalRates = new ArrayList<>(size);
        BigDecimal tempSumRate = BigDecimal.ZERO;
        for (IntegralActivityPrizeVo 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);

        IntegralActivityPrizeVo integralActivityPrizeVo = prizeList.get(index);
        return integralActivityPrizeVo;
    }

    /*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);
        }
    }*/

}
