package com.izouma.nineth.service;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alipay.api.AlipayClient;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.google.common.base.Splitter;
import com.huifu.adapay.core.exception.BaseAdaPayException;
import com.huifu.adapay.model.AdapayCommon;
import com.huifu.adapay.model.Payment;
import com.izouma.nineth.config.*;
import com.izouma.nineth.domain.Collection;
import com.izouma.nineth.domain.*;
import com.izouma.nineth.dto.MarketSettlement;
import com.izouma.nineth.dto.PageQuery;
import com.izouma.nineth.dto.UserBankCard;
import com.izouma.nineth.enums.*;
import com.izouma.nineth.event.*;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.repo.*;
import com.izouma.nineth.security.Authority;
import com.izouma.nineth.service.sms.SmsService;
import com.izouma.nineth.utils.*;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.BucketConfiguration;
import io.github.bucket4j.Refill;
import io.github.bucket4j.distributed.proxy.ProxyManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;

import javax.persistence.criteria.Join;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Service
@Slf4j
public class OrderService {

    private final OrderRepo                     orderRepo;
    private final CollectionRepo                collectionRepo;
    private final UserAddressRepo               userAddressRepo;
    private final UserRepo                      userRepo;
    private final Environment                   env;
    private final AlipayClient                  alipayClient;
    private final AlipayProperties              alipayProperties;
    private final WxPayService                  wxPayService;
    private final WxPayProperties               wxPayProperties;
    private final AssetService                  assetService;
    private final SysConfigService              sysConfigService;
    private final AssetRepo                     assetRepo;
    private final UserCouponRepo                userCouponRepo;
    private final CollectionService             collectionService;
    private final CommissionRecordRepo          commissionRecordRepo;
    private final AdapayProperties              adapayProperties;
    private final GeneralProperties             generalProperties;
    private final RocketMQTemplate              rocketMQTemplate;
    private final RedisTemplate<String, Object> redisTemplate;
    private final SnowflakeIdWorker             snowflakeIdWorker;
    private final SmsService                    smsService;
    private final ErrorOrderRepo                errorOrderRepo;
    private final ShowCollectionRepo            showCollectionRepo;
    private final ShowroomService               showroomService;
    private final CollectionPrivilegeRepo       collectionPrivilegeRepo;
    private final UserBankCardRepo              userBankCardRepo;
    private final CacheService                  cacheService;
    private final UserPropertyRepo              userPropertyRepo;
    private final UserBalanceService            userBalanceService;
    private final ProxyManager<String>          buckets;
    private final HeatInfoRepo                  heatInfoRepo;
    private final ShowroomRepo                  showroomRepo;

    public OrderService(OrderRepo orderRepo, CollectionRepo collectionRepo, UserAddressRepo userAddressRepo,
                        UserRepo userRepo, Environment env, AlipayClient alipayClient,
                        AlipayProperties alipayProperties, WxPayService wxPayService, WxPayProperties wxPayProperties,
                        AssetService assetService, SysConfigService sysConfigService, AssetRepo assetRepo,
                        UserCouponRepo userCouponRepo, CollectionService collectionService,
                        CommissionRecordRepo commissionRecordRepo, AdapayProperties adapayProperties,
                        GeneralProperties generalProperties, RocketMQTemplate rocketMQTemplate,
                        RedisTemplate<String, Object> redisTemplate, SnowflakeIdWorker snowflakeIdWorker,
                        SmsService smsService, ErrorOrderRepo errorOrderRepo, ShowCollectionRepo showCollectionRepo,
                        ShowroomService showroomService, CollectionPrivilegeRepo collectionPrivilegeRepo,
                        UserBankCardRepo userBankCardRepo, CacheService cacheService, UserPropertyRepo userPropertyRepo,
                        UserBalanceService userBalanceService, ProxyManager<String> proxyManager,
                        HeatInfoRepo heatInfoRepo, ShowroomRepo showroomRepo) {
        this.orderRepo = orderRepo;
        this.collectionRepo = collectionRepo;
        this.userAddressRepo = userAddressRepo;
        this.userRepo = userRepo;
        this.env = env;
        this.alipayClient = alipayClient;
        this.alipayProperties = alipayProperties;
        this.wxPayService = wxPayService;
        this.wxPayProperties = wxPayProperties;
        this.assetService = assetService;
        this.sysConfigService = sysConfigService;
        this.assetRepo = assetRepo;
        this.userCouponRepo = userCouponRepo;
        this.collectionService = collectionService;
        this.commissionRecordRepo = commissionRecordRepo;
        this.adapayProperties = adapayProperties;
        this.generalProperties = generalProperties;
        this.rocketMQTemplate = rocketMQTemplate;
        this.redisTemplate = redisTemplate;
        this.snowflakeIdWorker = snowflakeIdWorker;
        this.smsService = smsService;
        this.errorOrderRepo = errorOrderRepo;
        this.showCollectionRepo = showCollectionRepo;
        this.showroomService = showroomService;
        this.collectionPrivilegeRepo = collectionPrivilegeRepo;
        this.userBankCardRepo = userBankCardRepo;
        this.cacheService = cacheService;
        this.userPropertyRepo = userPropertyRepo;
        this.userBalanceService = userBalanceService;
        this.buckets = proxyManager;
        this.heatInfoRepo = heatInfoRepo;
        this.showroomRepo = showroomRepo;
    }


    public Page<Order> all(PageQuery pageQuery) {

        Page<Order> all = orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
        List<Order> content = all.getContent();
        content.forEach(order -> {
            Long userId = orderRepo.selectUserId(order.getAssetId());
            order.setSellerId(userId);
        });
        return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
    }

    public String mqCreate(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor,
                           String sign, boolean vip, boolean safeFlag, Long showroomId) {
        String qs = null;
        try {
            qs = AESEncryptUtil.decrypt(sign);
        } catch (Exception e) {
            throw new BusinessException("签名错误");
        }
        final Map<String, String> map = Splitter.on('&').trimResults().withKeyValueSeparator('=').split(qs);
        if (Math.abs(MapUtils.getLong(map, "ts") - System.currentTimeMillis()) > 90000) {
            throw new BusinessException("签名已过期");
        }

        if (redisTemplate.opsForValue().get(RedisKeys.BLACK_LIST + userId) != null) {
            throw new BusinessException("频繁操作，请稍后再试");
        }
        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.LIMIT_USER + userId);
        ops.setIfAbsent(0, Duration.ofSeconds(10));
        long val = Optional.ofNullable(ops.increment()).orElse(0L);
        if (val > 5) {
            if (val > 10) {
                redisTemplate.opsForValue().set(RedisKeys.BLACK_LIST + userId, 1, Duration.ofSeconds(60 * 10));
            }
            throw new BusinessException("频繁操作，请稍后再试");
        }

        limitReq(collectionId);

        Integer stock = collectionService.getStock(collectionId);
        if (stock == null || stock <= 0) {
            throw new BusinessException("藏品已售罄", ErrorCode.SOLD_OUT);
        }

        Long id = snowflakeIdWorker.nextId();
        redisTemplate.opsForValue().set("safeFlag::" + id, safeFlag, 10, TimeUnit.MINUTES);
        SendResult result = rocketMQTemplate.syncSend(generalProperties.getCreateOrderTopic(),
                new CreateOrderEvent(id, userId, collectionId, qty, addressId, userCouponId, invitor, vip, showroomId),
                100000);

        log.info("发送订单到队列: {}, userId={}, result={}", id, userId, result);
        return String.valueOf(id);
    }

    public void limitReq(Long collectionId) {
        Bucket bucket = buckets.builder().build("limit::" + collectionId, () -> (BucketConfiguration.builder()
                .addLimit(Bandwidth.classic(3000, Refill.intervally(3000, Duration.ofSeconds(3000))))
                .build()));
        if (!bucket.tryConsume(1)) {
            throw new BusinessException("前方拥堵，请稍后再试");
        }
    }

    public Order create(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor,
                        Long id, boolean vip, Long showroomId) {
        long t = System.currentTimeMillis();
        qty = 1;
        int stock = Optional.ofNullable(collectionService.decreaseStock(collectionId, qty))
                .map(Math::toIntExact)
                .orElseThrow(new BusinessException("很遗憾，藏品已售罄", ErrorCode.SOLD_OUT));

        int usePoint = 0;
        // 创建订单出错后需要回滚库存，所以需要try-catch
        try {
            if (stock < 0) {
                throw new BusinessException("很遗憾，藏品已售罄", ErrorCode.SOLD_OUT);
            }
            Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在"));
            if (collection.isInPaying()) {
                throw new BusinessException("当然藏品正在支付中");
            }
            if (collection.getAssetId() != null && collection.getAssetId().equals(778359L)) {
                throw new BusinessException("很遗憾，藏品已售罄", ErrorCode.SOLD_OUT);
            }
            if (collection.getSource() == CollectionSource.TRANSFER) {
                Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("藏品不存在"));
                if (Objects.equals(asset.getUserId(), userId)) {
                    throw new BusinessException("不能购买自己的藏品");
                }
                if (asset.getStatus() != AssetStatus.NORMAL) {
                    throw new BusinessException("藏品已下架");
                }
                if (asset.getLockTo() != null && asset.getLockTo().isAfter(LocalDateTime.now())) {
                    throw new BusinessException("此藏品已锁仓");
                }
            }
            User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("铸造者不存在"));
            UserCoupon coupon = null;
            if (collection.isCouponPayment()) {
                if (userCouponId == null) {
                    throw new BusinessException("必须使用优惠券支付");
                }
                coupon = userCouponRepo.findById(userCouponId).orElseThrow(new BusinessException("兑换券不存在"));
                if (!coupon.getUserId().equals(userId)) {
                    throw new BusinessException("兑换券不属于您");
                }
                if (coupon.isUsed()) {
                    throw new BusinessException("该兑换券已使用");
                }
                if (coupon.isLimited() && !coupon.getCollectionIds().contains(collectionId)) {
                    throw new BusinessException("该兑换券不可用");
                }
            }

            if (collection.isScheduleSale()) {
                if (collection.getStartTime().isAfter(LocalDateTime.now())) {
                    throw new BusinessException("当前还未开售");
                }
            }
            if (!collection.isOnShelf()) {
                if (!collection.isScanCode()) {
                    throw new BusinessException("藏品已下架");
                }
            }

            if (!collection.isSalable()) {
                throw new BusinessException("该藏品当前不可购买");
            }

            if (collection.getMaxCount() > 0) {
                int userMax = userPropertyRepo.findById(userId)
                        .map(UserProperty::getMaxCount)
                        .orElse(collection.getMaxCount());
                int count;
                if (StringUtils.isNotBlank(collection.getCountId())) {
                    count = orderRepo.countByUserIdAndCountIdAndStatusIn(userId, collection.getCountId(),
                            Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
                } else {
                    count = orderRepo.countByUserIdAndCollectionIdAndStatusIn(userId, collectionId,
                            Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
                }
                if (count >= userMax) {
                    throw new BusinessException("限购" + userMax + "件");
                }

            }
            User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));

            //设置了最低消费
            if (ObjectUtils.isNotEmpty(collection.getMinimumCharge()) && collection.getMinimumCharge()
                    .compareTo(BigDecimal.ZERO) > 0) {

                if (!user.isCanSale()) {
                    throw new BusinessException("绿洲石不足");
                }
            }


            //查询是否有拉新任务，只算官方购买
            if (collection.getSource() != CollectionSource.TRANSFER && collection.getAssignment() > 0) {
                //延迟销售
                if (!vip && collection.getTimeDelay()) {
                    if (collection.getSaleTime().isAfter(LocalDateTime.now())) {
                        throw new BusinessException("当前还未开售");
                    }
                }
                if (vip) {
                    if (user.getVipPurchase() < 1) {
                        throw new BusinessException("非vip！");
                    }
                } else {
                    if (user.getVipPoint() < 1) {
                        throw new BusinessException("没有购买名额");
                    }
                    usePoint = 1;
                }
            }

            UserAddress userAddress = null;
            if (addressId != null) {
                userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在"));
            }

            BigDecimal gasFee = sysConfigService.getBigDecimal("gas_fee");
            Order order = Order.builder()
                    .id(Optional.ofNullable(id).orElse(snowflakeIdWorker.nextId()))
                    .userId(userId)
                    .collectionId(collectionId)
                    .name(collection.getName())
                    .pic(collection.getPic())
                    .detail(collection.getDetail())
                    .properties(collection.getProperties())
                    .category(collection.getCategory())
                    .canResale(collection.isCanResale())
                    .royalties(collection.getRoyalties())
                    .serviceCharge(collection.getServiceCharge())
                    .type(collection.getType())
                    .source(collection.getSource())
                    .minterId(collection.getMinterId())
                    .minter(minter.getNickname())
                    .minterAvatar(minter.getAvatar())
                    .qty(qty)
                    .price(collection.getPrice())
                    .gasPrice(gasFee)
                    .totalPrice(collection.getPrice().multiply(BigDecimal.valueOf(qty)).add(gasFee))
                    .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null))
                    .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null))
                    .address(Optional.ofNullable(userAddress).map(u ->
                                    u.getProvinceName() + " " + u.getCityName() + " " + u.getDistrictName() + " " + u
                                            .getAddress())
                            .orElse(null))
                    .status(OrderStatus.NOT_PAID)
                    .assetId(collection.getAssetId())
                    .couponId(userCouponId)
                    .invitor(invitor)
                    .countId(collection.getCountId())
                    .vip(vip)
                    .vipPoint(usePoint)
                    .companyId(collection.getCompanyId())
                    .build();
            if (coupon != null) {
                coupon.setUsed(true);
                coupon.setUseTime(LocalDateTime.now());
                if (coupon.isNeedGas()) {
                    order.setTotalPrice(order.getGasPrice());
                } else {
                    order.setTotalPrice(BigDecimal.ZERO);
                }
                userCouponRepo.save(coupon);
            }

            if (collection.getSource() == CollectionSource.TRANSFER) {
                Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
                asset.setStatus(AssetStatus.TRADING);
                assetRepo.save(asset);
//                collectionRepo.setOnShelf(collectionId, false);
                collectionRepo.setInPaying(collectionId, true);
                //拥有指定藏品降税
                order.setRoyalties(assetService.getRoyalties(minter.getId(), collection.getRoyalties(), userId));
            }
            order = orderRepo.save(order);
            if (order.getTotalPrice().compareTo(BigDecimal.ZERO) == 0) {
                notifyOrder(order.getId(), PayMethod.WEIXIN, null);
            }

//            if (usePoint > 0) {
//                // 扣除积分
//                userRepo.addVipPoint(userId, -usePoint);
//                cacheService.clearUserMy(userId);
//            }

            if (ObjectUtils.isNotEmpty(showroomId)) {
                //通过展厅的购买数量
                heatInfoRepo.save(HeatInfo.builder()
                        .showroomId(showroomId)
                        .userId(userId)
                        .type(HeatType.BUY)
                        .value(0)
                        .orderId(order.getId())
                        .build());
            }
            rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), collectionId, 10000);
            log.info("订单创建完成, id={}, {}ms", order.getId(), System.currentTimeMillis() - t);
            return order;
        } catch (Exception e) {
            collectionService.increaseStock(collectionId, qty);
//            if (usePoint > 0) {
//                // 扣除积分
//                userRepo.addVipPoint(userId, usePoint);
//                cacheService.clearUserMy(userId);
//                log.info("订单失败加积分用户ID:{}, 积分:{}", userId, usePoint);
//            }
//            if (vip) {
//                collectionService.decreaseQuota(collectionId, 1);
//                log.info("订单失败加藏品额度CollectionId:{}", collectionId);
//            }
            throw e;
        }
    }

    public Object checkLimit(Long collectionId, Long userId) {
        Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在"));

        int count = 0;

        AtomicInteger userMax = new AtomicInteger();
        userPropertyRepo.findById(userId).ifPresent(userProperty -> userMax.set(userProperty.getMaxCount()));

        if (collection.getMaxCount() > 0) {
            if (StringUtils.isNotBlank(collection.getCountId())) {
                count = orderRepo.countByUserIdAndCountIdAndStatusIn(userId, collection.getCountId(),
                        Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
            } else {
                count = orderRepo.countByUserIdAndCollectionIdAndStatusIn(userId, collectionId,
                        Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
            }
        }
        Map<String, Object> map = new HashMap<>();
        map.put("limit", userMax.get() > 0 ? userMax.get() : collection.getMaxCount());
        map.put("count", count);
        return map;
    }

    public Object payOrderWeixin(Long id, String tradeType, String openId) throws WxPayException, EncoderException {
        Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在"));
        if (order.getStatus() != OrderStatus.NOT_PAID) {
            throw new BusinessException("订单状态错误");
        }

        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
        request.setBody(order.getName());
        request.setOutTradeNo(String.valueOf(new SnowflakeIdWorker(1, 1).nextId()));
        request.setTotalFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue());
        if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
            // 测试环境设为1分
//                    request.setTotalFee(1);
        }
        request.setSpbillCreateIp("180.102.110.170");
        request.setNotifyUrl(wxPayProperties.getNotifyUrl());
        request.setTradeType(tradeType);
        request.setOpenid(openId);
        request.setSignType("MD5");
        JSONObject body = new JSONObject();
        body.put("action", "payOrder");
        body.put("userId", order.getUserId());
        body.put("orderId", order.getId());
        request.setAttach(body.toJSONString());
        if (WxPayConstants.TradeType.MWEB.equals(tradeType)) {
            WxPayMwebOrderResult result = wxPayService.createOrder(request);
            return result.getMwebUrl() + "&redirect_url=" + new URLCodec().encode(wxPayProperties.getReturnUrl());
        } else if (WxPayConstants.TradeType.JSAPI.equals(tradeType)) {
            return wxPayService.<WxPayMpOrderResult>createOrder(request);
        }
        throw new BusinessException("不支持此付款方式");

    }

    @Cacheable(value = "adapay", key = "#id+'_'+#payChannel")
    public Object payAdapay(Long id, String payChannel, String openId) throws BaseAdaPayException {

        List<String> aliChannels = Arrays.asList("alipay", "alipay_qr", "alipay_wap");
        List<String> wxChannels = Arrays.asList("wx_pub", "wx_lite");
        if (!aliChannels.contains(payChannel) && !wxChannels.contains(payChannel)) {
            throw new BusinessException("不支持此渠道");
        }
        Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在"));
        if (SecurityUtils.getAuthenticatedUser() != null
                && !SecurityUtils.getAuthenticatedUser().getId().equals(order.getUserId())) {
            log.error("payAdapay userId错误 requestUserId={} orderUserId={}",
                    SecurityUtils.getAuthenticatedUser().getId(), order.getUserId());
        }
        Collection collection = collectionRepo.findById(order.getCollectionId())
                .orElseThrow(new BusinessException("藏品不存在"));
        User invitor = null;
        if (order.getInvitor() != null) {
            invitor = userRepo.findById(order.getInvitor()).orElse(null);
        }
        if (invitor != null && StringUtils.isBlank(invitor.getSettleAccountId())) {
            invitor = null;
        }
        if (order.getStatus() != OrderStatus.NOT_PAID) {
            throw new BusinessException("订单状态错误");
        }

        Map<String, Object> paymentParams = new HashMap<>();
        paymentParams.put("order_no", String.valueOf(snowflakeIdWorker.nextId()));
        paymentParams.put("pay_amt", order.getTotalPrice().setScale(2, RoundingMode.HALF_UP).toPlainString());
        paymentParams.put("app_id", adapayProperties.getAppId());
        paymentParams.put("pay_channel", payChannel);
        paymentParams.put("goods_title", collection.getName());
        paymentParams.put("goods_desc", collection.getName());
        paymentParams.put("time_expire", DateTimeFormatter.ofPattern("yyyyMMddHHmmss")
                .format(LocalDateTime.now().plusMinutes(3)));
        paymentParams.put("notify_url", adapayProperties.getNotifyUrl() + "/order/" + adapayProperties
                .getMerchant() + "/" + order.getId());

        List<Map<String, Object>> divMembers = new ArrayList<>();
        BigDecimal totalAmount = order.getTotalPrice().subtract(order.getGasPrice());
        BigDecimal restAmount = order.getTotalPrice().multiply(BigDecimal.valueOf(1));

        if (collection.getSource().equals(CollectionSource.TRANSFER)) {
            Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("无记录"));
            User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("拥有者用户不存在"));
            if (collection.getServiceCharge() + collection.getRoyalties() > 0) {
                // 扣除手续费、服务费、GAS费
                restAmount = divMoney(totalAmount, restAmount, divMembers, owner.getMemberId(),
                        100 - (collection.getServiceCharge() + collection.getRoyalties()), false);
            }
            restAmount = divMoney(restAmount, divMembers, "0", restAmount, true);
        } else {
            if (invitor != null && invitor.getShareRatio() != null
                    && invitor.getShareRatio().compareTo(BigDecimal.ZERO) > 0) {
                restAmount = divMoney(totalAmount, restAmount, divMembers, invitor.getMemberId(),
                        invitor.getShareRatio().intValue(), false);
            }
            restAmount = divMoney(restAmount, divMembers, "0", restAmount, true);
        }

        if (restAmount.compareTo(BigDecimal.ZERO) != 0) {
            log.error("分账出错 {}", JSON.toJSONString(divMembers, SerializerFeature.PrettyFormat));
            throw new BusinessException("分账出错");
        }
        if (divMembers.size() > 1) {
            paymentParams.put("div_members", divMembers);
        }
        Map<String, Object> expend = new HashMap<>();
        paymentParams.put("expend", expend);
        if ("wx_pub".equals(payChannel)) {
            if (StringUtils.isBlank(openId)) {
                throw new BusinessException("缺少openId");
            }
            expend.put("open_id", openId);
            expend.put("limit_pay", "1");
        }

        Map<String, Object> response;
        if ("wx_lite".equals(payChannel)) {
            paymentParams.put("adapay_func_code", "wxpay.createOrder");
            paymentParams.put("callback_url", generalProperties.getHost() + "/9th/orders");
            response = AdapayCommon.requestAdapayUits(paymentParams);
            log.info("createOrderResponse {}", JSON.toJSONString(response, SerializerFeature.PrettyFormat));
        } else {
            response = Payment.create(paymentParams);
            log.info("createOrderResponse {}", JSON.toJSONString(response, SerializerFeature.PrettyFormat));
            AdapayService.checkSuccess(response);

            // 保存adapay的订单id，用于后续取消订单时的查询
            BoundSetOperations<String, Object> ops = redisTemplate.boundSetOps(RedisKeys.PAY_RECORD + order.getId());
            ops.add(adapayProperties.getMerchant() + "#" + MapUtils.getString(response, "id"));
            ops.expire(7, TimeUnit.DAYS);
        }

        switch (payChannel) {
            case "alipay_wap":
            case "alipay":
                return MapUtils.getString(MapUtils.getMap(response, "expend"), "pay_info");
            case "alipay_qr":
                return MapUtils.getString(MapUtils.getMap(response, "expend"), "qrcode_url");
            case "wx_pub":
                JSONObject payParams = JSON
                        .parseObject(MapUtils.getString(MapUtils.getMap(response, "expend"), "pay_info"));
                payParams.put("timestamp", payParams.get("timeStamp"));
                payParams.remove("timeStamp");
                return payParams;
            default:
                return MapUtils.getMap(response, "expend");
        }
    }

    public static BigDecimal divMoney(BigDecimal totalAmount, BigDecimal restAmount, List<Map<String, Object>> divMembers,
                                      String memberId, double ratio, boolean feeFlag) {
        if (ratio == -1 || (ratio > 0 && ratio < 100)) {
            BigDecimal divAmount = ratio == -1 ? restAmount :
                    totalAmount.multiply(BigDecimal.valueOf(ratio))
                            .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
            Map<String, Object> divMem = new HashMap<>();
            divMem.put("member_id", memberId);
            divMem.put("amount", divAmount.toPlainString());
            divMem.put("fee_flag", feeFlag ? "Y" : "N");
            divMembers.add(divMem);
            return restAmount.subtract(divAmount);
        } else {
            throw new BusinessException("分账比例错误");
        }
    }

    public static BigDecimal divMoney(BigDecimal restAmount, List<Map<String, Object>> divMembers,
                                      String memberId, BigDecimal divAmount, boolean feeFlag) {
        if (divAmount.compareTo(BigDecimal.ZERO) > 0) {
            Map<String, Object> divMem = new HashMap<>();
            divMem.put("member_id", memberId);
            divMem.put("amount", divAmount.toPlainString());
            divMem.put("fee_flag", feeFlag ? "Y" : "N");
            divMembers.add(divMem);
        }
        return restAmount.subtract(divAmount);
    }

    public void notifyOrder(Long orderId, PayMethod payMethod, String transactionId) {
        log.info("订单回调 orderId: {}, payMethod: {}, transactionId: {}", orderId, payMethod, transactionId);

        // 取消订单与订单回调不能同时进行，需要抢锁
        if (!getOrderLock(orderId)) {
            log.info("订单回调失败 orderId: {} redis锁定, 重新发送到队列", orderId);
            rocketMQTemplate.syncSend(generalProperties.getOrderNotifyTopic(),
                    new OrderNotifyEvent(orderId, payMethod, transactionId, System.currentTimeMillis()));
            return;
        }

        try {
            Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
            Collection collection = collectionRepo.findDetailById(order.getCollectionId())
                    .orElseThrow(new BusinessException("藏品不存在"));

            User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在"));
            if (order.getStatus() == OrderStatus.NOT_PAID) {
                order.setStatus(OrderStatus.PROCESSING);
                order.setPayTime(LocalDateTime.now());
                order.setTransactionId(transactionId);
                order.setPayMethod(payMethod);
                if (order.getType() == CollectionType.BLIND_BOX) {
                    log.info("开始盲盒抽卡 orderId: {}, collectionId: {}", orderId, collection.getId());
                    BlindBoxItem winItem = collectionService.draw(order.getUserId(), collection.getId());
                    log.info("抽卡成功 orderId: {}, collectionId: {}, winCollectionId: {}", orderId, collection
                            .getId(), winItem.getCollectionId());
                    order.setWinCollectionId(winItem.getCollectionId());
                    orderRepo.save(order);

                    //藏品其他信息/是否vip
                    CollectionPrivilege collectionPrivilege = collectionPrivilegeRepo
                            .findByCollectionId(order.getCollectionId());
                    if (ObjectUtils.isNotEmpty(collectionPrivilege)) {
                        if (collectionPrivilege.isVip()) {
                            //更新vip信息
                            userRepo.updateVipPurchase(order.getUserId(), 1);
                        }
                    }

                    assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售",
                            winItem.getTotal() > 1 ? collectionService.getNextNumber(winItem.getCollectionId()) : null,
                            collection.getHoldDays(), false);


                } else {
                    if (collection.getSource() == CollectionSource.TRANSFER) {
                        orderRepo.save(order);
                        Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null);
                        boolean safeFlag = Objects
                                .equals(true, redisTemplate.opsForValue().get("safeFlag::" + orderId));
                        assetService.transfer(asset, order.getPrice(), user, TransferReason.TRANSFER,
                                order.getId(), safeFlag);
                        order.setStatus(OrderStatus.FINISH);
                        orderRepo.save(order);
                        collectionRepo.delete(collection);
                        // 如果展厅有此藏品
                        showCollectionRepo.deleteAllByCollectionId(order.getCollectionId());

                        // 发送短信提醒用户转让成功
                        if (asset != null && asset.getUserId() != null) {
                            smsService.sellOut(userRepo.findPhoneById(asset.getUserId()));
                        }
                        userBalanceService.realtimeSettleOrder(order);
                    } else {
                        orderRepo.save(order);
                        //藏品其他信息/是否vip
                        CollectionPrivilege collectionPrivilege = collectionPrivilegeRepo
                                .findByCollectionId(order.getCollectionId());
                        if (ObjectUtils.isNotEmpty(collectionPrivilege)) {
                            if (collectionPrivilege.isVip()) {
                                //更新vip信息
                                userRepo.updateVipPurchase(order.getUserId(), 1);
                            }
                        }
                        Asset asset = assetService.createAsset(collection, user, order.getId(), order.getPrice(),
                                "出售", collectionService.getNextNumber(collection), false);

                        if (collection.getType() == CollectionType.SHOWROOM) {
                            showroomService.save(asset);
                        }
                    }
                }
                commission(order);
                if (collection.getAssetId() == null) {
                    collectionService.increaseSale(order.getCollectionId(), order.getQty());
                }

                //通过展厅购买加热力值
                List<HeatInfo> heatInfos = heatInfoRepo
                        .findByUserIdAndOrderIdAndType(order.getUserId(), orderId, HeatType.BUY);
                if (CollUtil.isNotEmpty(heatInfos)) {
                    HeatInfo heatInfo = heatInfos.get(0);
                    int weight = sysConfigService.getInt("heat_buy_weight");
                    heatInfo.setValue(weight);
                    heatInfoRepo.save(heatInfo);
                    showroomRepo.addHeat(heatInfo.getShowroomId(), weight);
                }

            } else {
                throw new BusinessException("状态错误 " + order.getStatus());
            }
        } catch (Exception e) {
            ErrorOrder errorOrder = ErrorOrder.builder()
                    .orderId(orderId)
                    .transactionId(transactionId)
                    .payMethod(payMethod)
                    .build();
            if (e instanceof BusinessException) {
                log.error("订单回调出错 orderId: {} {}", orderId, e.getMessage());
            } else {
                log.error("订单回调出错 orderId: " + orderId, e);
            }
            errorOrder.setErrorMessage(e.getMessage());
            errorOrderRepo.save(errorOrder);

        }
        releaseOrderLock(orderId);
    }


    @EventListener
    public void onCreateAsset(CreateAssetEvent event) {
        Asset asset = event.getAsset();
        if (asset.getOrderId() != null) {
            Order order = orderRepo.findById(asset.getOrderId()).orElse(null);
            if (event.isSuccess() && order != null) {
                order.setTxHash(asset.getTxHash());
                order.setGasUsed(asset.getGasUsed());
                order.setBlockNumber(asset.getBlockNumber());
                order.setStatus(OrderStatus.FINISH);
                orderRepo.save(order);
            }
        }
    }

    @EventListener
    public void onHcMinted(MintedEvent event) {
        Asset asset = assetRepo.findById(event.getAssetId()).orElse(null);
        if (asset != null && asset.getOrderId() != null) {
            orderRepo.findById(asset.getOrderId()).ifPresent(order -> {
                order.setHcTxHash(asset.getTxHash());
                order.setHcGasUsed(asset.getGasUsed());
                order.setHcBlockNumber(asset.getBlockNumber());
                orderRepo.save(order);
            });
        }
    }

    @EventListener
    public void onTransferAsset(TransferAssetEvent event) {
        Asset asset = event.getAsset();
        Order order = orderRepo.findById(asset.getOrderId()).orElseThrow(new BusinessException("订单不存在"));
        if (event.isSuccess()) {
            order.setTxHash(asset.getTxHash());
            order.setGasUsed(asset.getGasUsed());
            order.setBlockNumber(asset.getBlockNumber());
            order.setStatus(OrderStatus.FINISH);
            orderRepo.save(order);
        } else {
            log.error("创建asset失败");
        }
    }

    public void cancel(Long id) {
        Order order = orderRepo.findById(id).orElseThrow(new BusinessException("订单不存在"));
        cancel(order);
    }

    public void cancel(Order order) {
        // 取消订单与订单回调不能同时进行，需要抢锁
        if (!getOrderLock(order.getId())) {
            log.error("订单取消失败 {}, redis锁了", order.getId());
            return;
        }

        try {
            if (order.getStatus() != OrderStatus.NOT_PAID) {
                throw new BusinessException("当前订单状态无法取消[" + order.getStatus().name() + "]");
            }

            CollectionSource source = Optional.ofNullable(order.getSource()).orElseGet(() ->
                    collectionRepo.findById(order.getCollectionId()).map(Collection::getSource).orElse(null));

            if (source == CollectionSource.TRANSFER) {
                Asset asset = assetRepo.findById(order.getAssetId()).orElse(null);
                if (asset != null) {
                    log.info("set normal cancelOrder {}", order.getId());
                    asset.setStatus(AssetStatus.NORMAL);
                    assetRepo.save(asset);
                }
//                collectionRepo.setOnShelf(order.getCollectionId(), true);
                collectionRepo.setInPaying(order.getCollectionId(), false);
            }
            collectionService.increaseStock(order.getCollectionId(), order.getQty());

            order.setStatus(OrderStatus.CANCELLED);
            order.setCancelTime(LocalDateTime.now());
            orderRepo.save(order);

            if (order.getCouponId() != null) {
                userCouponRepo.findById(order.getCouponId()).ifPresent(coupon -> {
                    coupon.setUsed(false);
                    coupon.setUseTime(null);
                    userCouponRepo.save(coupon);
                });
            }
            //加上积分
            if (ObjectUtils.isNotEmpty(order.getVipPoint()) && order.getVipPoint() > 0) {
                userRepo.addVipPoint(order.getUserId(), order.getVipPoint());
                cacheService.clearUserMy(order.getUserId());
                log.info("取消加积分用户ID:{},订单ID:{},积分:{}", order.getUserId(), order.getId(), order.getVipPoint());
            }
            if (order.isVip()) {
                collectionService.decreaseQuota(order.getCollectionId(), 1);
                log.info("取消加藏品额度CollectionId:{}", order.getCollectionId());
            }
            rocketMQTemplate.syncSend(generalProperties.getUpdateQuotaTopic(), order.getCollectionId(), 10000);
            log.info("取消订单{}", order.getId());
        } catch (Exception e) {
            if (e instanceof BusinessException) {
                log.error(e.getMessage());
            } else {
                log.error("订单取消错误 orderId: " + order.getId(), e);
            }
        }
        releaseOrderLock(order.getId());
    }

    public void refundCancelled(Order order) {
    }

    public void setNumber() {
        for (Collection collection : collectionRepo.findAll()) {
            if (collection.getSource() != CollectionSource.OFFICIAL) continue;
            collection.setCurrentNumber(0);
            collectionRepo.save(collection);
            for (Asset asset : assetRepo.findByCollectionId(collection.getId())) {
                if (asset.getStatus() == AssetStatus.GIFTED || asset.getStatus() == AssetStatus.TRANSFERRED) {

                } else {
                    asset.setNumber(collectionService.getNextNumber(collection.getId()));
                    assetRepo.save(asset);
                }
            }
        }
    }

    public void setNumberRecursive(Asset asset) {
    }


    @Scheduled(cron = "0 0 4 * * ?")
    public void setSales() {
        if (generalProperties.isNotifyServer()) {
            return;
        }
        List<User> minters = userRepo.findByAuthoritiesContains(Authority.get(AuthorityName.ROLE_MINTER));
        for (User minter : minters) {
            userRepo.setSales(minter.getId(), (int) orderRepo.countSales(minter.getId()));
        }
    }

    public void commission(Order order) {
        if (order.getInvitor() != null) {
            userRepo.findById(order.getInvitor()).ifPresent(user -> {
                BigDecimal shareRatio = user.getShareRatio();
                if (StringUtils.isNotBlank(user.getSettleAccountId()) &&
                        shareRatio != null && shareRatio.compareTo(BigDecimal.ZERO) > 0) {
                    BigDecimal totalPrice = order.getTotalPrice().subtract(order.getGasPrice());
                    commissionRecordRepo.save(CommissionRecord.builder()
                            .orderId(order.getId())
                            .collectionId(order.getCollectionId())
                            .name(order.getName())
                            .totalPrice(totalPrice)
                            .nickname(user.getNickname())
                            .userId(user.getId())
                            .shareRatio(user.getShareRatio())
                            .phone(user.getPhone())
                            .shareAmount(totalPrice.multiply(shareRatio)
                                    .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP))
                            .build());
                }
            });
        }
    }

    public Object queryCreateOrder(String id) {
        Object res = redisTemplate.opsForValue().get(RedisKeys.CREATE_ORDER + id);
        if (res != null) {
            if (res instanceof Map) {
                if (MapUtils.getBooleanValue((Map) res, "success", false)) {
                    Order order = (Order) MapUtils.getObject((Map) res, "data");
                    if (!SecurityUtils.getAuthenticatedUser().getId().equals(order.getUserId())) {
                        log.error("queryCreateOrder userId错误 requestUserId={} orderUserId={}",
                                SecurityUtils.getAuthenticatedUser().getId(), order.getUserId());
                        return null;
                    }
                }
            }
        }
        return res;
    }

    // 获取订单锁，有效时间1小时
    public boolean getOrderLock(Long orderId) {
        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.ORDER_LOCK + orderId);
        Boolean flag = ops.setIfAbsent(1, 1, TimeUnit.HOURS);
        return Boolean.TRUE.equals(flag);
    }

    // 释放订单锁
    public void releaseOrderLock(Long orderId) {
        redisTemplate.delete(RedisKeys.ORDER_LOCK + orderId);
    }

    public void calcSettle(LocalDateTime start, LocalDateTime end, OutputStream outputStream) {
        List<Order> orders = orderRepo
                .findByCreatedAtBetweenAndSourceAndStatusIn(start, end, CollectionSource.TRANSFER, Arrays
                        .asList(OrderStatus.PROCESSING, OrderStatus.FINISH));
        List<Asset> assets = assetRepo.findAllById(orders.stream().map(Order::getAssetId).collect(Collectors.toSet()));
        List<UserBankCard> bankCards = userBankCardRepo
                .findByUserIdIn(assets.stream().map(Asset::getUserId).collect(Collectors.toSet()));
        List<MarketSettlement> settlements = new ArrayList<>();
        for (Order order : orders) {
            BigDecimal amount = order.getTotalPrice()
                    .subtract(order.getGasPrice())
                    .multiply(new BigDecimal("100")
                            .subtract(BigDecimal.valueOf(order.getServiceCharge()))
                            .subtract(BigDecimal.valueOf(order.getRoyalties()))
                            .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP))
                    .setScale(2, RoundingMode.HALF_UP);
            Long userId = assets.stream().filter(a -> a.getId().equals(order.getAssetId())).map(Asset::getUserId)
                    .findAny().orElse(null);
            if (userId != null) {
                UserBankCard userBankCard = bankCards.stream().filter(b -> b.getUserId().equals(userId)).findAny()
                        .orElse(null);
                MarketSettlement marketSettlement = settlements.stream().filter(s -> s.getUserId().equals(userId))
                        .findAny().orElse(null);
                if (marketSettlement == null) {
                    marketSettlement = new MarketSettlement(userId,
                            Optional.ofNullable(userBankCard).map(UserBankCard::getRealName).orElse(null),
                            Optional.ofNullable(userBankCard).map(UserBankCard::getBankNo).orElse(null),
                            amount);
                    settlements.add(marketSettlement);
                } else {
                    marketSettlement.setAmount(marketSettlement.getAmount()
                            .add(amount));
                }
            }
        }
        EasyExcel.write(outputStream, MarketSettlement.class).sheet("sheet").doWrite(settlements);
    }

    @Scheduled(cron = "0 0/5 * * * ?")
    public void setBlackList() {
        List<Long> userIds = orderRepo
                .checkBlackList(LocalDateTime.now(), LocalDateTime.now().plusHours(1));
        userIds.forEach(userId -> {
            redisTemplate.opsForValue().set(RedisKeys.BLACK_LIST + userId, 1, Duration.ofSeconds(60 * 60));
        });
    }

    public List<Order> addOrder(Long collectionId, List<Long> userIds, LocalDateTime time, boolean notify) {
        Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在"));
        User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("用户不存在"));
        int i = 0;
        List<Order> list = new ArrayList<>();
        for (Long userId : userIds) {
            time = time.plusSeconds(i++);
            BigDecimal gasFee = new BigDecimal("1");
            Order order = Order.builder()
                    .id(snowflakeIdWorker.nextId())
                    .userId(userId)
                    .collectionId(collectionId)
                    .name(collection.getName())
                    .pic(collection.getPic())
                    .detail(collection.getDetail())
                    .properties(collection.getProperties())
                    .category(collection.getCategory())
                    .canResale(collection.isCanResale())
                    .royalties(collection.getRoyalties())
                    .serviceCharge(collection.getServiceCharge())
                    .type(collection.getType())
                    .source(collection.getSource())
                    .minterId(collection.getMinterId())
                    .minter(minter.getNickname())
                    .minterAvatar(minter.getAvatar())
                    .qty(1)
                    .price(collection.getPrice())
                    .gasPrice(gasFee)
                    .totalPrice(collection.getPrice().multiply(BigDecimal.valueOf(1)).add(gasFee))
                    .status(notify ? OrderStatus.NOT_PAID : OrderStatus.FINISH)
                    .assetId(collection.getAssetId())
                    .countId(collection.getCountId())
                    .build();
            orderRepo.saveAndFlush(order);

            String txHash = RandomStringUtils.randomAlphanumeric(64).toLowerCase(Locale.ROOT);
            String transactionId = DateTimeUtils.format(LocalDateTime.now(), "yyyyMMdd") +
                    RandomStringUtils.randomNumeric(24);
            BigInteger gas = new BigInteger("157377");

            if (notify) {
                rocketMQTemplate.syncSend(generalProperties.getOrderNotifyTopic(),
                        new OrderNotifyEvent(order.getId(), PayMethod.ALIPAY, order.getTransactionId()
                                , System.currentTimeMillis()));
            } else {
                order.setCreatedAt(ObjectUtils.clone(time));
                order.setPayTime(time.plusSeconds(RandomUtils.nextInt(5, 50)));
                order.setTransactionId(transactionId);
                order.setTxHash(txHash);
                order.setGasUsed(gas);
                order.setPayMethod(PayMethod.ALIPAY);
                order.setStatus(OrderStatus.FINISH);
                order.setModifiedAt(order.getPayTime());
                order.setModifiedBy("system");
                order.setCreatedBy("system");
                orderRepo.save(order);
            }
            list.add(order);
        }
        return list;
    }

    public Page<Order> byTag(Long tagId, List<Long> excludeUserId, Pageable pageable) {
        if (excludeUserId.isEmpty()) {
            excludeUserId.add(0L);
        }
        return orderRepo.findAll((root, query, criteriaBuilder) -> {
            Join join = root.join("tags");
            return criteriaBuilder.and(criteriaBuilder.equal(join.get("id"), tagId),
                    criteriaBuilder.equal(root.get("status"), OrderStatus.FINISH),
                    criteriaBuilder.equal(root.get("source"), CollectionSource.TRANSFER),
                    criteriaBuilder.not(root.get("ownerId").in(excludeUserId)));
        }, pageable);
    }

    @Async
    public void refundGas() throws ExecutionException, InterruptedException {
        new ForkJoinPool(500).submit(() -> {
            List<Order> list = orderRepo.findByCollectionId(8657801L).stream()
                    .filter(o -> o.getStatus() == OrderStatus.FINISH || o.getStatus() == OrderStatus.PROCESSING)
                    .collect(Collectors.toList());
            list.parallelStream().forEach(o -> {
                log.info("refundGas {}", o.getId());
                userBalanceService.modifyBalance(o.getUserId(), new BigDecimal(1), BalanceType.REFUND, null, false, null);
            });
        }).get();
    }
}
