package com.liquidnet.service.order.utils;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.commons.lang.util.*;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.candy.vo.CandyUseResultVo;
import com.liquidnet.service.dragon.dto.DragonRefundAppDto;
import com.liquidnet.service.dragon.service.IDragonOrderRefundsService;
import com.liquidnet.service.goblin.constant.GoblinRedisConst;
import com.liquidnet.service.goblin.constant.GoblinStatusConst;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinGoodsAnticipateValueVo;
import com.liquidnet.service.goblin.dto.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Slf4j
public class GoblinNftOrderUtils {

    @Value("${liquidnet.service.candy.url}")
    private String candyUrl;
    @Value("${liquidnet.service.order.url-pay.nftRefundNotify}")
    private String synUrl;
    /*@Value("${liquidnet.service.dragon.urls.refundApply}")
    private String refundApply;*/

    @Autowired
    private QueueUtils queueUtils;
    @Autowired
    private GoblinRedisUtils goblinRedisUtils;
    @Autowired
    private GoblinNftOrderUtils goblinNftOrderUtils;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private GoblinMongoUtils goblinMongoUtils;
    @Autowired
    private GoblinOrderUtils goblinOrderUtils;

    @Autowired
    private IDragonOrderRefundsService dragonOrderRefundsService;

    // 库存
    public int decrSkuStock(String skuId, Integer stock) {
        String redisKey = GoblinRedisConst.REAL_STOCK_SKU.concat(skuId);
        return (int) redisUtil.decr(redisKey, stock);
    }

    public int incrSkuStock(String skuId, Integer stock) {
        String redisKey = GoblinRedisConst.REAL_STOCK_SKU.concat(skuId);
        return (int) redisUtil.incr(redisKey, stock);
    }

    // 回滚用户sku购买个数和库存
    public void backSkuCountAndStock(String listId, String uid, String stockSkuId, String bySkuId, int number) {
        // 减少用户购买个数
        goblinRedisUtils.decrSkuCountByUid(uid, bySkuId, number);
        // 增加库存
        goblinRedisUtils.incrSkuStock(listId, stockSkuId, number);
    }

    // 订单详情vo
    public void setNftOrder(GoblinNftOrderVo vo) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_INFO.concat(vo.getOrderId());
        redisUtil.set(redisKey, vo);
    }

    // 获取 订单详情vo
    public GoblinNftOrderVo getNftOrder(String orderId) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_INFO.concat(orderId);
        Object obj = redisUtil.get(redisKey);
        if (obj == null) {
            return null;
        } else {
            return (GoblinNftOrderVo) obj;
        }
    }

    // code换id
    public void setNftOrderIdOfCode(String orderCode, String orderId) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_ID_OF_CODE.concat(orderCode);
        redisUtil.set(redisKey, orderId);
    }

    // code换id
    public String getNftOrderIdOfCode(String orderCode) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_ID_OF_CODE.concat(orderCode);
        Object obj = redisUtil.get(redisKey);
        if (obj == null) {
            return "";
        } else {
            return (String) obj;
        }
    }

    // 添加 订单id列表
    public void addNftOrderList(String uid, String orderId) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_USER_ID_LIST.concat(uid);
        List<String> list = getNftOrderList(uid);
        if (list.size() >= 40) {
            list.remove(0);
            list.add(orderId);
        } else {
            list.add(orderId);
        }
        redisUtil.set(redisKey, list);
    }

    // 获取 订单id列表
    public List<String> getNftOrderList(String uid) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_USER_ID_LIST.concat(uid);
        Object obj = redisUtil.get(redisKey);
        if (obj == null) {
            return CollectionUtil.arrayListString();
        } else {
            return (List<String>) obj;
        }
    }

    // 退款订单vo
    public void setBackOrderVo(GoblinNftOrderRefundVo vo) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_REFUND_INFO.concat(vo.getOrderId());
        redisUtil.set(redisKey, vo);
    }

    public boolean setNftOrderBuyLock(String userId) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_BUG_LOCK
                .concat(userId);
        return redisUtil.lock(redisKey, 1, 60);
    }

    public boolean setNftOrderExLock(String code) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_EX_LOCK
                .concat(code);
        return redisUtil.lock(redisKey, 1, 60);
    }

    public void delNftOrderBuyLock(String userId) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_BUG_LOCK
                .concat(userId);
        redisUtil.uLock(redisKey);
    }

    public void delNftOrderExLock(String code) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_EX_LOCK
                .concat(code);
        redisUtil.uLock(redisKey);
    }

    /**
     * 兑换码
     */
    public GoblinNftExCodeVo getGoblinNftExCodeVo(String code) {
        String redisKey = GoblinRedisConst.ACTIVITY_SKU_CODE.concat(code);
        Object obj = redisUtil.get(redisKey);
        if (obj == null) {
            return null;
        } else {
            return (GoblinNftExCodeVo) obj;
        }
    }

    public void setGoblinNftExCodeVo(GoblinNftExCodeVo codeVo) {
        String redisKey = GoblinRedisConst.ACTIVITY_SKU_CODE.concat(codeVo.getCode());
        redisUtil.set(redisKey, codeVo);
    }

    public int incrExCountByUid(String uid, String skuId, String activityId, int number) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_EX_BUY_COUNT.concat(activityId + ':').concat(skuId + ':').concat(uid);
        return (int) redisUtil.incr(redisKey, number);
    }

    public int decrExCountByUid(String uid, String skuId, String activityId, int number) {
        String redisKey = GoblinRedisConst.REDIS_GOBLIN_EX_BUY_COUNT.concat(activityId + ':').concat(skuId + ':').concat(uid);
        return (int) redisUtil.decr(redisKey, number);
    }

    // skuId 是否开启预约
    public boolean getValueBySkuId(String skuId) {
        String redisKey = GoblinRedisConst.ANTICIPATE_VALUE_SKUID.concat(skuId);

        Object obj = redisUtil.get(redisKey);
        if (obj == null) {
            return false;
        } else {
            GoblinGoodsAnticipateValueVo anticipateValueVo = (GoblinGoodsAnticipateValueVo) obj;
            return anticipateValueVo.getType() != 1;
        }
    }

    // 是否预约过 没开启true
    public boolean getUserAboutAut(String skuId, String uid) {
        boolean valueBySkuId = getValueBySkuId(skuId);
        if (valueBySkuId) {
            String redisKey = GoblinRedisConst.USER_ANTICIPATE_STATE.concat(skuId).concat(uid);
            Object obj = redisUtil.get(redisKey);
            if (obj == null) {
                return false;
            } else {
                return true;
            }
        } else {
            return true;
        }
    }

    /**
     * 使用平台优惠券 只支持代金券
     *
     * @param uCouponId  券id
     * @param content    消费内容
     * @param totalPrice 订单总价
     * @param spuId      spuId
     * @param uid        uid
     * @return
     */
    public GoblinUseResultVo useCoupon(String uCouponId, String content, BigDecimal totalPrice, String spuId, String uid) {
        MultiValueMap<String, String> params = CollectionUtil.linkedMultiValueMapStringString();
        params.add("uCouponId", uCouponId);
        params.add("content", content);
        params.add("totalPrice", totalPrice.toString());
        params.add("goodId", spuId);
        params.add("performanceId", "null");
        params.add("timeId", "null");
        params.add("ticketId", "null");
        params.add("uid", uid);

        MultiValueMap<String, String> header = CollectionUtil.linkedMultiValueMapStringString();
        header.add("Authorization", "Bearer " + CurrentUtil.getToken());
        header.add("Accept", "application/json;charset=UTF-8");

        String returnData = HttpUtil.post(candyUrl.concat("/candy-coupon/use"), params, header);
        ResponseDto<CandyUseResultVo> useResultVo = JsonUtils.fromJson(returnData, new TypeReference<ResponseDto<CandyUseResultVo>>() {
        });
        CandyUseResultVo candyUseResultVo = useResultVo.getData();
        Integer type = candyUseResultVo.getCouType();
        BigDecimal value = candyUseResultVo.getValue();
        BigDecimal voucher;
        switch (type) {// 券类型[-1-不可用 | 1-代金券｜2-满减券｜3-兑换券｜4-折扣券|101-优先券]
            case -1:
                voucher = BigDecimal.ZERO;
                break;
            case 1:
                if (totalPrice.compareTo(value) >= 0) {
                    voucher = value;
                } else {// 优惠金额大于了总价 优惠金额最大为总价
                    voucher = totalPrice;
                }
                break;
            default:// 其他类型如果使用成功了直接退掉 返回不可用
                type = -1;
                voucher = BigDecimal.ZERO;
                goblinOrderUtils.backCoupon(uCouponId, uid);
                break;
        }
        GoblinUseResultVo returnVo = GoblinUseResultVo.getNew();
        returnVo.setValue(voucher.setScale(2, BigDecimal.ROUND_HALF_UP));
        returnVo.setCouType(String.valueOf(type));
        return returnVo;

    }

    /**
     * 使用店铺优惠券
     *
     * @param uCouponId  券id
     * @param content    消费内容
     * @param totalPrice 订单总价
     * @param spuId      spuId
     * @param uid        uid
     * @return
     */
    public GoblinUseResultVo useStoreCoupon(String uCouponId, String content, BigDecimal totalPrice, String spuId, String uid) {
        List<GoblinUserCouponVo> voList = goblinRedisUtils.getUserCouponVos(uid);
        GoblinUseResultVo returnVo = GoblinUseResultVo.getNew();
        returnVo.setValue(BigDecimal.ZERO);
        returnVo.setCouType("-1");
        LocalDateTime now = LocalDateTime.now();

        for (GoblinUserCouponVo vo : voList) {
            if (vo.getUcouponId().equals(uCouponId) && vo.getDuedAt().isAfter(now) && vo.getState().equals(1) && vo.getUseScope().equals("1")) {// 判断可用
                List<String> couponSpuIds = goblinRedisUtils.getStoreCouponSpuIds(vo.getStoreCouponId());
                if (CollectionUtil.isEmpty(couponSpuIds)) {
                    break;
                }
                // 判断是否在可用商品内
                BigDecimal contentPrice = BigDecimal.ZERO;
                for (String item : couponSpuIds) {
                    if (spuId.equals(item)) {
                        contentPrice = totalPrice;
                    }
                }
                if (vo.getTriggers().compareTo(contentPrice) <= 0) {// 订单金额大于等于触发金额可使用
                    if (vo.getType().equals("1")) {//代金券
                        if (vo.getValFace().compareTo(contentPrice) > 0) {
                            returnVo.setValue(contentPrice);
                        } else {
                            returnVo.setValue(vo.getValFace());
                        }
                        returnVo.setCouType(vo.getType());
                    } else {
                        break;
                    }
                    // 更新券信息
                    vo.setState(5);
                    vo.setUsedFor(content);
                    GoblinUserCouponBasicVo basicVo = GoblinUserCouponBasicVo.getNew();
                    basicVo.setState(5);
                    basicVo.setUsedFor(content);
                    goblinMongoUtils.changeCouponVos(vo.getUcouponId(), basicVo);
                    queueUtils.sendMsgByRedis(MQConst.GoblinQueue.SQL_COUPON.getKey(),
                            SqlMapping.get("goblin_user_coupon.updateState", vo.getState(), vo.getUsedFor(), LocalDateTime.now(), vo.getUcouponId()));
                }
                break;
            }
        }
        // 如果使用了更新redis
        if (!returnVo.getCouType().equals("-1")) {
            goblinRedisUtils.setUserCouponVos(uid, voList);
        }
        return returnVo;
    }

    //超时支付自动退款
    public Boolean refundOrderSku(String orderId, String paymentId, String paymentType, String payType) {
        LocalDateTime now = LocalDateTime.now();
        GoblinNftOrderVo nftOrder = getNftOrder(orderId);
        BigDecimal refundPrice = nftOrder.getPriceActual();
        //记录退款单
        String refundCode = IDGenerator.storeRefundCode(nftOrder.getOrderCode());
        GoblinNftOrderRefundVo backOrderVo = GoblinNftOrderRefundVo.getNew();
        backOrderVo.setOrderRefundId(IDGenerator.nextTimeId2());
        backOrderVo.setRefundCode(refundCode);
        backOrderVo.setOrderId(orderId);
        backOrderVo.setOrderCode(nftOrder.getOrderCode());
        backOrderVo.setStoreId(nftOrder.getStoreId());
        backOrderVo.setUserId(nftOrder.getUserId());
        backOrderVo.setPrice(refundPrice);
        backOrderVo.setStatus(GoblinStatusConst.NftStatus.ORDER_REFUND_STATUS_1.getValue());
        backOrderVo.setCreatedAt(now);
        backOrderVo.setErrorReason("");

        if (!payType.equals("applepay")) {// 苹果支付不做处理
            // 调用退款 超时支付回调可能还未完成 所以redisVo没有此俩个参数
            nftOrder.setPaymentId(paymentId);
            nftOrder.setPaymentType(paymentType);
            String returnString = initRefund(nftOrder, refundPrice, refundCode);
            HashMap hashMapResult = JsonUtils.fromJson(returnString, HashMap.class);
            Boolean success = (Boolean) hashMapResult.get("success");
            String message = (String) hashMapResult.get("message");
            if (!success) {
                if (!Objects.equals(backOrderVo.getStatus(), GoblinStatusConst.NftStatus.ORDER_REFUND_STATUS_2.getValue())) {
                    backOrderVo.setStatus(GoblinStatusConst.NftStatus.ORDER_REFUND_STATUS_3.getValue());
                    backOrderVo.setErrorReason(message);
                }
            }
        }

        /**
         * 退款表数据处理
         */
        setBackOrderVo(backOrderVo);// redis
        goblinMongoUtils.insertGoblinNftOrderRefundVo(backOrderVo);// mongo
        queueUtils.sendMsgByRedis(// mysql
                MQConst.GoblinQueue.GOBLIN_NFT_ORDER.getKey(),
                SqlMapping.get("goblin_nft_order_refund.insert",
                        backOrderVo.getOrderRefundId(), backOrderVo.getRefundCode(), backOrderVo.getOrderId(),
                        backOrderVo.getOrderCode(), backOrderVo.getStoreId(), backOrderVo.getUserId(),
                        backOrderVo.getPrice(), backOrderVo.getStatus(),
                        backOrderVo.getErrorReason(), now
                )
        );

        return true;
    }

    private String initRefund(GoblinNftOrderVo orderVo, BigDecimal price, String refundCode) {
        log.info("NFT退款请求 参数：[orderVo:{}, price:{}, refundCode:{}]", orderVo, price, refundCode);
        /*MultiValueMap<String, String> params = CollectionUtil.linkedMultiValueMapStringString();
        params.add("code", orderVo.getPayCode());
        params.add("notifyUrl", synUrl);
        params.add("orderCode", orderVo.getOrderCode());
        params.add("orderRefundCode", refundCode);
        params.add("paymentId", orderVo.getPaymentId());
        params.add("paymentType", orderVo.getPaymentType());
        params.add("price", String.valueOf(price));
        params.add("priceTotal", String.valueOf(price));
        params.add("reason", "NFT超时支付");
        MultiValueMap<String, String> headers = CollectionUtil.linkedMultiValueMapStringString();
        headers.add("Accept", "application/json;charset=UTF-8");
        String returnString = HttpUtil.post(refundApply, params, headers);
        log.info("NFT退款请求 结果：[returnString:{}]", returnString);
        return returnString;*/

        ResponseDto<DragonRefundAppDto> returnData = dragonOrderRefundsService.dragonRefund(
                orderVo.getPayCode(),
                orderVo.getPayCode(),
                refundCode,
                "NFT超时支付",
                synUrl,
                price,
                orderVo.getPaymentType(),
                orderVo.getPaymentId(),
                price);
        String returnString = JsonUtils.toJson(returnData);
        log.info("NFT退款请求 结果：[returnString:{}]", returnString);
        return returnString;
    }

    public boolean isVipMember(String uid) {
        if (goblinRedisUtils.getMember(uid) != null) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * private int skuType 商品类型[0-常规|1-数字藏品]
     * private String status 审核状态[0-初始编辑|1-审核中|2-审核不通过|3-审核通过];
     * private String shelvesStatus 单品上架状态[0-待上架|1-下架|2-违规|3-上架];
     * private String skuAppear 是否隐藏[0-默认展示|1-隐藏]; 隐藏款 拼参数可购买 盲盒可抽
     * private String delFlg 删除标记[0-未删除|1-删除];
     * <p>
     * private LocalDateTime saleStartTime 开售时间;
     * private LocalDateTime saleStopTime 停售时间 预留 暂时不做处理;
     * private String soldoutStatus 是否售罄[0-否|1-是];
     * <p>
     * private String skuCanbuy 是否购买[0-否|1-是] 这个用来预览 前端自己判断;
     * <p>
     * Integer upchain NFT上传声明状态[0-待上传|1-已声明|2-声明失败|9-声明中]
     * <p>
     * 根据概率判断是否过滤当前sku 没设置概率或者设置了的返回true
     * <p>
     * String unbox; 是否盲盒[0-否|1-是]
     */
    // 获取盲盒下藏品的库存 各种状态下不能算库存的排除掉
    public int getSkuAllStatusStock(String listId, GoblinGoodsSkuInfoVo info) {
        if (
                info != null
//            && LocalDateTime.now().isAfter(info.getSaleStartTime())
                        && (null == info.getSoldoutStatus() || info.getSoldoutStatus().equals("0"))
                        && (null == info.getSkuCanbuy() || info.getSkuCanbuy().equals("1"))
                        && (null == info.getHitRatio() || info.getHitRatio().compareTo(BigDecimal.ZERO) > 0)
        ) {// 可以返回库存
            // 外面只有盲盒才能请求进来 里面只有不是分批购才判断盲盒里藏品的开售时间
            if (null == listId) {
//                if (LocalDateTime.now().isAfter(info.getSaleStartTime())) {
                return goblinRedisUtils.getSkuStock(listId, info.getSkuId());
//                } else {
//                    return 0;
//                }
            } else {
                return goblinRedisUtils.getSkuStock(listId, info.getSkuId());
            }
        } else {// 不计入库存
            return 0;
        }
    }

    // 各种状态下判断藏品是否可以展示
    public boolean getSkuAllStatusShow(GoblinGoodsSkuInfoVo info) {
        if (
                info != null
                        && info.getSkuType() == 1
                        && info.getStatus().equals("3")
                        && info.getShelvesStatus().equals("3")
//            && (info.getSkuAppear() == null || info.getSkuAppear().equals("0"))
                        && info.getDelFlg().equals("0")
                        && ((info.getUnbox().equals("0") && info.getUpchain() == 1) || info.getUnbox().equals("1"))
        ) {
            return true;
        } else {
            return false;
        }
    }

    // 获取盲盒下藏品的库存 各种状态下不能算库存的排除掉 不考虑状态
    public int getSkuAllStatusStockUnStatus(String listId, GoblinGoodsSkuInfoVo info) {
        if (
                info != null
                        && (null == info.getHitRatio() || info.getHitRatio().compareTo(BigDecimal.ZERO) > 0)
        ) {// 可以返回库存
            // 外面只有盲盒才能请求进来 里面只有不是分批购才判断盲盒里藏品的开售时间
            if (null == listId) {
                return goblinRedisUtils.getSkuStock(listId, info.getSkuId());
            } else {
                return goblinRedisUtils.getSkuStock(listId, info.getSkuId());
            }
        } else {// 不计入库存
            return 0;
        }
    }

    //不考虑状态
    public boolean getSkuAllStatusShowUnStatus(GoblinGoodsSkuInfoVo info) {
        if (
                info != null
                        && info.getSkuType() == 1
                        && info.getDelFlg().equals("0")
                        && ((info.getUnbox().equals("0") && info.getUpchain() == 1) || info.getUnbox().equals("1"))
        ) {
            return true;
        } else {
            return false;
        }
    }

    public GoblinGoodsSkuInfoVo lotteryDraw(List<String> skuIdList, int number, LocalDateTime nowTime, String listId) {
//    private HashMap<String, Object> lotteryDraw(List<String> skuIdList, int number, LocalDateTime nowTime) {
        try {
            ArrayList<GoblinGoodsSkuInfoVo> skuInfoVos = ObjectUtil.cloneArrayGoblinGoodsSkuInfoListVo();
//            ArrayList<String> listIds = ObjectUtil.cloneArrayListString();
            for (String kid : skuIdList) {
                HashMap<String, Object> map = goblinRedisUtils.getGoodsSkuInfoVo(nowTime, kid);
                GoblinGoodsSkuInfoVo skuInfoVo = (GoblinGoodsSkuInfoVo) map.get("vo");
//                String listId = (String) map.get("listId");
                // 是盲盒的扔掉
                if (skuInfoVo.getUnbox().equals("1")) {
                    continue;
                }
                // 不能购买的 没库存的 概率是0的 过滤
                if (getSkuAllStatusShow(skuInfoVo) && getSkuAllStatusStock(listId, skuInfoVo) > 0) {
                    skuInfoVos.add(skuInfoVo);
//                    listIds.add(listId);
                }
            }
            if (CollectionUtil.isEmpty(skuInfoVos)) {
                return null;
            } else {
                // 计算总概率 和 剩余没填概率的平均概率
                int size = 0;
                BigDecimal sumHitRatio = BigDecimal.ZERO;
                for (GoblinGoodsSkuInfoVo skuInfoVo : skuInfoVos) {
                    if (null == skuInfoVo.getHitRatio()) {
                        size++;
                    } else {
                        sumHitRatio = sumHitRatio.add(skuInfoVo.getHitRatio());
                    }
                }
                /**
                 * 剔除掉没库存的商品再去算未设置概率商品的平均概率 会导致未设置概率的商品的概率增加
                 * 如果不剔除掉没库存的商品去算未设置概率商品的平均概率 会导致总概率小于100 如果随机数还是0-100的话就有可能会中奖溢出
                 * 想保持概率 1.不剔除无库存商品算平均概率 2.随机数最大值为排序后概率的最后一个值
                 */
                BigDecimal avgHitRatio = BigDecimal.ZERO;
                if (size > 0) {// 说明有未设置抽奖概率的
                    avgHitRatio = new BigDecimal(100).subtract(sumHitRatio).divide(new BigDecimal(size), 2, RoundingMode.HALF_UP);
                }

                // 未设置概率的写入概率
                ArrayList<GoblinGoodsSkuInfoVo> newSkuInfoVos = ObjectUtil.cloneArrayGoblinGoodsSkuInfoListVo();
//                ArrayList<String> newListIds = ObjectUtil.cloneArrayListString();
                int skuListSize = skuInfoVos.size();
                for (int i = 0; i < skuListSize; i++) {
                    GoblinGoodsSkuInfoVo infoVo = skuInfoVos.get(i);
                    if (null == infoVo.getHitRatio() || infoVo.getHitRatio().compareTo(BigDecimal.ZERO) < 0) {
                        /**
                         * 算所得平均概率是0 即中不了 剔除掉
                         * 但是这么处理会导致前端页面显示有库存 买的时候显示没有
                         * 所以要么概率加起来必须100 要么前端计算盲盒总概率的时候也要同样处理
                         */
                        if (avgHitRatio.compareTo(BigDecimal.ZERO) <= 0) {
                            continue;
                        } else {
                            infoVo.setHitRatio(avgHitRatio);
                        }
                    }
                    // 等于0的最终概率就是设置的值 大于0最终的概率是自己的+上面的
                    if (i > 0) {
                        infoVo.setHitRatio(skuInfoVos.get(i - 1).getHitRatio().add(infoVo.getHitRatio()));
                    }
                    newSkuInfoVos.add(infoVo);
//                    newListIds.add(listIds.get(i));
                }


                if (CollectionUtil.isEmpty(newSkuInfoVos)) {
                    log.info("该盲盒概率超过100导致不能卖 skuIdList:{}", skuIdList);
                    return null;
                }
                // 按照概率排序 按照上面的运算最后一定是最大的不用排序了
//                List<GoblinGoodsSkuInfoVo> listSort = newSkuInfoVos.stream().sorted(Comparator.comparing(GoblinGoodsSkuInfoVo::getHitRatio)).collect(Collectors.toList());
                List<BigDecimal> hitRatioList = newSkuInfoVos.stream().map(GoblinGoodsSkuInfoVo::getHitRatio).collect(Collectors.toList());
                // 根据区块值来获取抽取到的物品索引
                double nextDouble = Math.random();
                BigDecimal nextDoubleNew = BigDecimal.valueOf(nextDouble);
                nextDoubleNew = nextDoubleNew.multiply(hitRatioList.get(hitRatioList.size() - 1)).setScale(4, RoundingMode.HALF_UP);

                hitRatioList.add(nextDoubleNew);
                Collections.sort(hitRatioList);

                int index = hitRatioList.indexOf(nextDoubleNew);

                GoblinGoodsSkuInfoVo goodsSkuInfoVo = newSkuInfoVos.get(index);
//                String endListId = newListIds.get(index);
                // 判断库存
                int surplusGeneral = goblinRedisUtils.decrSkuStock(listId, goodsSkuInfoVo.getSkuId(), number);
                log.error("剩余 skuId = " + surplusGeneral);
                if (surplusGeneral < 0) {
                    goblinRedisUtils.incrSkuStock(listId, goodsSkuInfoVo.getSkuId(), number);
                    return lotteryDraw(skuIdList, number, nowTime, listId);
                } else {
//                    HashMap<String, Object> map = CollectionUtil.mapStringObject();
//                    map.put("goodsSkuInfoVo", goodsSkuInfoVo);
//                    map.put("listId", endListId);
//                    return map;
                    return goodsSkuInfoVo;
                }
            }
        } catch (Exception e) {
            log.error("NFT下单-抽盲盒异常", e);
            return null;
        }
    }

    // 不判断状态
    public GoblinGoodsSkuInfoVo lotteryDrawUnStatus(List<String> skuIdList, int number, LocalDateTime nowTime, String listId) {
        try {
            ArrayList<GoblinGoodsSkuInfoVo> skuInfoVos = ObjectUtil.cloneArrayGoblinGoodsSkuInfoListVo();
            for (String kid : skuIdList) {
                HashMap<String, Object> map = goblinRedisUtils.getGoodsSkuInfoVo(nowTime, kid);
                GoblinGoodsSkuInfoVo skuInfoVo = (GoblinGoodsSkuInfoVo) map.get("vo");
                // 是盲盒的扔掉
                if (skuInfoVo.getUnbox().equals("1")) {
                    continue;
                }
                // 不能购买的 没库存的 概率是0的 过滤
                if (getSkuAllStatusShowUnStatus(skuInfoVo) && getSkuAllStatusStockUnStatus(listId, skuInfoVo) > 0) {
                    skuInfoVos.add(skuInfoVo);
                }
            }
            if (CollectionUtil.isEmpty(skuInfoVos)) {
                return null;
            } else {
                // 计算总概率 和 剩余没填概率的平均概率
                int size = 0;
                BigDecimal sumHitRatio = BigDecimal.ZERO;
                for (GoblinGoodsSkuInfoVo skuInfoVo : skuInfoVos) {
                    if (null == skuInfoVo.getHitRatio()) {
                        size++;
                    } else {
                        sumHitRatio = sumHitRatio.add(skuInfoVo.getHitRatio());
                    }
                }
                /**
                 * 剔除掉没库存的商品再去算未设置概率商品的平均概率 会导致未设置概率的商品的概率增加
                 * 如果不剔除掉没库存的商品去算未设置概率商品的平均概率 会导致总概率小于100 如果随机数还是0-100的话就有可能会中奖溢出
                 * 想保持概率 1.不剔除无库存商品算平均概率 2.随机数最大值为排序后概率的最后一个值
                 */
                BigDecimal avgHitRatio = BigDecimal.ZERO;
                if (size > 0) {// 说明有未设置抽奖概率的
                    avgHitRatio = new BigDecimal(100).subtract(sumHitRatio).divide(new BigDecimal(size), 2, RoundingMode.HALF_UP);
                }

                // 未设置概率的写入概率
                ArrayList<GoblinGoodsSkuInfoVo> newSkuInfoVos = ObjectUtil.cloneArrayGoblinGoodsSkuInfoListVo();
//                ArrayList<String> newListIds = ObjectUtil.cloneArrayListString();
                int skuListSize = skuInfoVos.size();
                for (int i = 0; i < skuListSize; i++) {
                    GoblinGoodsSkuInfoVo infoVo = skuInfoVos.get(i);
                    if (null == infoVo.getHitRatio() || infoVo.getHitRatio().compareTo(BigDecimal.ZERO) < 0) {
                        /**
                         * 算所得平均概率是0 即中不了 剔除掉
                         * 但是这么处理会导致前端页面显示有库存 买的时候显示没有
                         * 所以要么概率加起来必须100 要么前端计算盲盒总概率的时候也要同样处理
                         */
                        if (avgHitRatio.compareTo(BigDecimal.ZERO) <= 0) {
                            continue;
                        } else {
                            infoVo.setHitRatio(avgHitRatio);
                        }
                    }
                    // 等于0的最终概率就是设置的值 大于0最终的概率是自己的+上面的
                    if (i > 0) {
                        infoVo.setHitRatio(skuInfoVos.get(i - 1).getHitRatio().add(infoVo.getHitRatio()));
                    }
                    newSkuInfoVos.add(infoVo);
//                    newListIds.add(listIds.get(i));
                }


                if (CollectionUtil.isEmpty(newSkuInfoVos)) {
                    log.info("该盲盒概率超过100导致不能卖 skuIdList:{}", skuIdList);
                    return null;
                }
                // 按照概率排序 按照上面的运算最后一定是最大的不用排序了
//                List<GoblinGoodsSkuInfoVo> listSort = newSkuInfoVos.stream().sorted(Comparator.comparing(GoblinGoodsSkuInfoVo::getHitRatio)).collect(Collectors.toList());
                List<BigDecimal> hitRatioList = newSkuInfoVos.stream().map(GoblinGoodsSkuInfoVo::getHitRatio).collect(Collectors.toList());
                // 根据区块值来获取抽取到的物品索引
                double nextDouble = Math.random();
                BigDecimal nextDoubleNew = BigDecimal.valueOf(nextDouble);
                nextDoubleNew = nextDoubleNew.multiply(hitRatioList.get(hitRatioList.size() - 1)).setScale(4, RoundingMode.HALF_UP);

                hitRatioList.add(nextDoubleNew);
                Collections.sort(hitRatioList);

                int index = hitRatioList.indexOf(nextDoubleNew);

                GoblinGoodsSkuInfoVo goodsSkuInfoVo = newSkuInfoVos.get(index);
//                String endListId = newListIds.get(index);
                // 判断库存
                int surplusGeneral = goblinRedisUtils.decrSkuStock(listId, goodsSkuInfoVo.getSkuId(), number);
                log.error("剩余 skuId = " + surplusGeneral);
                if (surplusGeneral < 0) {
                    goblinRedisUtils.incrSkuStock(listId, goodsSkuInfoVo.getSkuId(), number);
                    return lotteryDraw(skuIdList, number, nowTime, listId);
                } else {
//                    HashMap<String, Object> map = CollectionUtil.mapStringObject();
//                    map.put("goodsSkuInfoVo", goodsSkuInfoVo);
//                    map.put("listId", endListId);
//                    return map;
                    return goodsSkuInfoVo;
                }
            }
        } catch (Exception e) {
            log.error("NFT下单-抽盲盒异常", e);
            return null;
        }
    }


    /**
     * 生成我的藏品
     *
     * @param orderVo
     */
    public void generateUserArtwork(GoblinNftOrderVo orderVo, Integer source) {
        String uid = orderVo.getUserId(), skuId = orderVo.getSkuId(), orderId = orderVo.getOrderId(), spuId = orderVo.getSpuId();
        if (goblinRedisUtils.hasGenUserDigitalArtwork(uid, skuId, orderId)) {// 已生成
            log.warn("#GEN_ARTWORK EXIST_ART[uid={},skuId={},orderId={},source={}]", uid, skuId, orderId, source);
            return;
        }
        GoblinGoodsInfoVo goodsInfoVo = goblinRedisUtils.getGoodsInfoVo(spuId);
        LocalDateTime now = LocalDateTime.now();

        GoblinUserDigitalArtworkVo digitalArtworkVo = GoblinUserDigitalArtworkVo.getNew();
//        String artworkId = IDGenerator.nextMilliId2();
        String artworkId = IDGenerator.nextSnowId();
        digitalArtworkVo.setArtworkId(artworkId);
        digitalArtworkVo.setSkuId(skuId);
        digitalArtworkVo.setUid(uid);
        digitalArtworkVo.setOrderId(orderId);
        digitalArtworkVo.setSource(source);
        /*digitalArtworkVo.setState(source == 31 ? 5 : 0);*/
        if (source == 5) {
            digitalArtworkVo.setState(0);
        } else {
            digitalArtworkVo.setState(source == 31 ? 5 : 0);
        }
        digitalArtworkVo.setDelFlg("0");
        digitalArtworkVo.setCreatedAt(now);
        digitalArtworkVo.setAuthor(goodsInfoVo.getAuthor());
        digitalArtworkVo.setPublisher(goodsInfoVo.getPublisher());

        // Redis记录VO
        goblinRedisUtils.setGoblinUserDigitalArtworkVo(digitalArtworkVo);
        // Redis更新藏品ID列表
        goblinRedisUtils.addGoblinUserDigitalArtworkIds(uid, artworkId);
        // 缓存用户藏品数
        goblinRedisUtils.incrGoblinUserDigitalArtworks(uid);
        // Redis生成标记（缓存三天），用于上面的生成检查
        goblinRedisUtils.markGenUserDigitalArtwork(uid, skuId, orderId);
        {// Redis更新藏品订单关联artworkId
            orderVo.setArtworkId(artworkId);
            goblinNftOrderUtils.setNftOrder(orderVo);
            // Mongo同步更新藏品订单
            goblinOrderUtils.setMongoList(GoblinNftOrderVo.class.getSimpleName(), "orderId", orderVo.getOrderId(), GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_INFO, 2);
        }

        // Mongo记录VO
        goblinOrderUtils.setMongoList(GoblinUserDigitalArtworkVo.class.getSimpleName(), "artworkId", digitalArtworkVo.getArtworkId(), GoblinRedisConst.USER_DIGITAL_ARTWORK, 1);
//        goblinMongoUtils.insertGoblinUserDigitalArtworkVo(digitalArtworkVo);

        if (source != 31) {// 空投需要领取再发行
            GoblinGoodsSkuInfoVo goodsSkuInfoVo = goblinRedisUtils.getGoodsSkuInfoVo(skuId);
            if ("0".equals(goodsSkuInfoVo.getUnbox())) {// 非盲盒，NFT发行购买
                ObjectNode bizNftBuyNode = JsonUtils.OM().createObjectNode().put("nftOrderPayId", orderId).put("routerType", goodsSkuInfoVo.getRouteType())
                        .put("skuId", skuId).put("userId", uid).put("buyTimestamp", DateUtil.Formatter.yyyyMMddHHmmss.format(now));
                /*queueUtils.sendMsgByRedis(MQConst.GalaxyQueue.JSON_NFT_PUBLISH_AND_BUY.getKey(), bizNftBuyNode.toString());*/
                if (source != 5) {
                    queueUtils.sendMsgByRedis(MQConst.GalaxyQueue.JSON_NFT_PUBLISH_AND_BUY.getKey(), bizNftBuyNode.toString());
                }
            }
        }

        // Mysql持久化
        LinkedList<String> toMqSqls = CollectionUtil.linkedListString();
        toMqSqls.add(SqlMapping.get("goblin_user_digital_artwork.insert"));
        LinkedList<Object[]> initUserDigitalArtworkObjs = CollectionUtil.linkedListObjectArr();
        initUserDigitalArtworkObjs.add(new Object[]{digitalArtworkVo.getArtworkId(), skuId, uid, orderId, source, digitalArtworkVo.getState(), now});
        toMqSqls.add(SqlMapping.get("goblin_nft_order.update_artwork"));
        LinkedList<Object[]> updateNftOrderObjs = CollectionUtil.linkedListObjectArr();
        updateNftOrderObjs.add(new Object[]{artworkId, orderId});
        queueUtils.sendMsgByRedis(MQConst.GoblinQueue.SQL_ARTWORK_GEN.getKey(), SqlMapping.gets(toMqSqls, initUserDigitalArtworkObjs, updateNftOrderObjs));
    }
}
