package com.izouma.nineth.service;

import cn.hutool.core.date.DateUtil;
import com.izouma.nineth.domain.TokenHistory;
import com.izouma.nineth.config.Constants;
import com.izouma.nineth.config.GeneralProperties;
import com.izouma.nineth.config.RedisKeys;
import com.izouma.nineth.config.SysConstants;
import com.izouma.nineth.domain.Collection;
import com.izouma.nineth.domain.*;
import com.izouma.nineth.domain.Order;
import com.izouma.nineth.dto.AssetDTO;
import com.izouma.nineth.dto.PageQuery;
import com.izouma.nineth.dto.PageWrapDTO;
import com.izouma.nineth.dto.UserHistory;
import com.izouma.nineth.dto.drawback.DrawbackDTO;
import com.izouma.nineth.dto.drawback.HoldAssetDate;
import com.izouma.nineth.enums.*;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.lock.RedisLockable;
import com.izouma.nineth.repo.*;
import com.izouma.nineth.service.pay.UniformPayUtil;
import com.izouma.nineth.utils.JpaUtils;
import com.izouma.nineth.utils.SecurityUtils;
import com.izouma.nineth.utils.TokenUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.hibernate.query.criteria.internal.OrderImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import javax.transaction.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLConnection;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
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
@AllArgsConstructor
@Slf4j
public class AssetService {

    private AssetRepo assetRepo;
    private UserRepo userRepo;
    private CollectionRepo collectionRepo;
    private OrderRepo orderRepo;
    private TokenHistoryRepo tokenHistoryRepo;
    private SysConfigService sysConfigService;
    private RocketMQTemplate rocketMQTemplate;
    private GeneralProperties generalProperties;
    private ActivityPriorityDocumentRepo activityPriorityDocumentRepo;
    private UserWareHouseRepo userWareHouseRepo;
    private RedisTemplate redisTemplate;
    private UniformPayUtil uniformPayUtil;

    private AssetSellLimitRepo assetSellLimitRepo;

    private CollectionService collectionService;

    private final ActivityCollectionService activityCollectionService;

    private EntityManager entityManager;

    /**
     * 统计金额信息
     * @return
     */
    public BigDecimal sumPrice(Specification specification){
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<BigDecimal> criteriaQuery = criteriaBuilder.createQuery(BigDecimal.class);
        Root<Asset> root = criteriaQuery.from(Asset.class);
        criteriaQuery.select(
                criteriaBuilder.sum(root.get("price"))
        );
        Predicate predicate = specification.toPredicate(root, criteriaQuery, criteriaBuilder);
        criteriaQuery.where(predicate);

        TypedQuery<BigDecimal> query = entityManager.createQuery(criteriaQuery);
        return query.getSingleResult();
    }

    /**
     * 列表查询
     * @param pageQuery
     * @return
     */
    public PageWrapDTO<Asset> all(PageQuery pageQuery) {
        pageQuery.getQuery().put("del", false);
        String collectionCategoryId = (String) pageQuery.getQuery().get("collectionCategoryId");
        Boolean isCollection = null;
        if (pageQuery.getQuery().containsKey("isCollection")) {
            isCollection = (Boolean) pageQuery.getQuery().get("isCollection");
            if (Boolean.FALSE.equals(isCollection)){
                pageQuery.getQuery().put("variableType",true);
            }
        }
        pageQuery.getQuery().remove("collectionCategoryId");
        pageQuery.getQuery().remove("isCollection");
        Specification<Asset> specification = JpaUtils.toSpecification(pageQuery, Asset.class);
        PageRequest pageRequest = JpaUtils.toPageRequest(pageQuery);
        if (pageRequest.getSort().stream().noneMatch(order -> order.getProperty().equals("createdAt"))) {
            pageRequest = PageRequest.of(pageRequest.getPageNumber(), pageQuery.getSize(),
                    pageRequest.getSort().and(Sort.by("createdAt").descending()));
        }
        Boolean finalIsCollection = isCollection;
        specification = specification.and((Specification<Asset>) (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> and = new ArrayList<>();
            if (StringUtils.isNotBlank(collectionCategoryId)) {
                and.add(criteriaBuilder.gt(criteriaBuilder.function("find_in_set", Integer.class, criteriaBuilder.literal(collectionCategoryId), root.get("categories")), 0));
            }
            if (finalIsCollection != null && !finalIsCollection){
                and.add(criteriaBuilder.isNotNull(root.get("blindBoxId")));
            }
            if (finalIsCollection != null && finalIsCollection){
                and.add(criteriaBuilder.isNull(root.get("blindBoxId")));
            }
            return criteriaBuilder.and(and.toArray(new Predicate[0]));
        });

        Page<Asset> page = assetRepo.findAll(specification, pageRequest);

        PageWrapDTO pageWrapDTO = new PageWrapDTO(page.toList(), page.getPageable(), page.getTotalElements());
        //统计总金额
        BigDecimal totalAmount = sumPrice(specification);
        pageWrapDTO.setTotalAmount(totalAmount);
        return pageWrapDTO;
    }


    /**
     * 列表查询(同类藏品合集)
     * @param pageQuery
     * @return
     */
    public Page<AssetDTO> allSameCollection(PageQuery pageQuery) {
        pageQuery.getQuery().put("del", false);
        //类型
        String collectionCategoryId = (String) pageQuery.getQuery().remove("collectionCategoryId");
        Boolean isCollection = (Boolean) pageQuery.getQuery().remove("isCollection");
        if (Boolean.FALSE.equals(isCollection)){
            pageQuery.getQuery().put("variableType",true);
        }

        Specification<Asset> specification = JpaUtils.toSpecification(pageQuery, Asset.class);

        PageRequest pageRequest = JpaUtils.toPageRequest(pageQuery);
        if (pageRequest.getSort().stream().noneMatch(order -> order.getProperty().equals("createdAt"))) {
            pageRequest = PageRequest.of(pageRequest.getPageNumber(), pageQuery.getSize(),
                    pageRequest.getSort().and(Sort.by("createdAt").descending()));
        }

        Boolean finalIsCollection = isCollection;
        specification = specification.and((Specification<Asset>) (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> and = new ArrayList<>();
            if (StringUtils.isNotBlank(collectionCategoryId)) {
                and.add(criteriaBuilder.gt(criteriaBuilder.function("find_in_set", Integer.class, criteriaBuilder.literal(collectionCategoryId), root.get("categories")), 0));
            }
            if (finalIsCollection != null && !finalIsCollection){
                and.add(criteriaBuilder.isNotNull(root.get("blindBoxId")));
            }
            if (finalIsCollection != null && finalIsCollection){
                and.add(criteriaBuilder.isNull(root.get("blindBoxId")));
            }
            return criteriaBuilder.and(and.toArray(new Predicate[0]));
        });

        //自定义查询
        List<AssetDTO> dataList;
        {
            CriteriaBuilder builder = entityManager.getCriteriaBuilder();
            CriteriaQuery<AssetDTO> criteriaQuery = builder.createQuery(AssetDTO.class);
            Root<Asset> root = criteriaQuery.from(Asset.class);
            Predicate predicate = specification.toPredicate(root, criteriaQuery, builder);
            criteriaQuery.multiselect(
                    root.get("userId"),
                    root.get("name"),
                    builder.min(root.get("pic")),
                    builder.min(root.get("minter")),
                    builder.min(root.get("minterAvatar")),
                    root.get("collectionId"),
                    builder.count(root.get("id"))
            );
            criteriaQuery.where(predicate);
            criteriaQuery.groupBy(
                    root.get("userId"),
                    root.get("name"),
                    root.get("collectionId")
            );

            List<javax.persistence.criteria.Order> orderList = pageRequest.getSort().stream().map(item -> {
                return new OrderImpl(builder.max(root.get(item.getProperty())), item.getDirection().isAscending());
            }).collect(Collectors.toList());
            criteriaQuery.orderBy(orderList);

            TypedQuery<AssetDTO> typedQuery = entityManager.createQuery(criteriaQuery);

            typedQuery.setFirstResult((int) pageRequest.getOffset());
            typedQuery.setMaxResults(pageRequest.getPageSize());

            dataList = typedQuery.getResultList();
        }

        //统计总数量
        Long total;
        {
            CriteriaBuilder builder = entityManager.getCriteriaBuilder();
            CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
            Root<Asset> root = criteriaQuery.from(Asset.class);
            Predicate predicate = specification.toPredicate(root, criteriaQuery, builder);
            criteriaQuery.select(
                    builder.countDistinct(root.get("collectionId"))
            );
            criteriaQuery.where(predicate);
            //汇总
            TypedQuery<Long> query = entityManager.createQuery(criteriaQuery);

            total = query.getSingleResult();
        }

        Page<AssetDTO> page = new PageImpl<>(dataList, pageRequest, total);
        return page;
    }



    /**
     * 补充其他数据
     * @param assets
     * @return
     */
    public List<AssetDTO> convertToAssetDTO(List<Asset> assets) {
        List<AssetDTO> assetDTOS = assets.stream().map(AssetDTO::new).collect(Collectors.toList());
        for (AssetDTO asset : assetDTOS) {
            //设置优先购权益
            PreEmptionPrivilege preEmptionPrivilege = preEmptionPrivilege(asset);
            asset.setPreEmptionPrivilege(preEmptionPrivilege);
            //手机号
            String phone = userRepo.findPhone(asset.getUserId());
            asset.setPhone(phone);
        }

        return assetDTOS;
    }

    /**
     * 查询优先购权益
     * @param assetDTO
     * @return
     */
    public PreEmptionPrivilege preEmptionPrivilege(AssetDTO assetDTO) {
        List<Map<String, Object>> maps = activityPriorityDocumentRepo.findByCollectionIds();
        Map<Long, Long> collectionIdsMap = new HashMap<>();
        maps.forEach(item -> {
            Object collectionId = item.get("collection_id");
            Object itemId = item.get("item_id");
            if (collectionId != null && itemId != null) {
                collectionIdsMap.put(Long.parseLong(collectionId + ""), Long.parseLong(itemId + ""));
            }
        });

        List<UserWareHouse> list = userWareHouseRepo.findByAssetId(assetDTO.getId());
        if (!list.isEmpty()) {
            UserWareHouse ac = list.get(0);
            return new PreEmptionPrivilege(ac.getAdvancePurchaseCount(), ac.getItemId());
        } else if (collectionIdsMap.containsKey(assetDTO.getCollectionId())) {
            Long count = collectionIdsMap.get(assetDTO.getCollectionId());
            return new PreEmptionPrivilege(0, count == null ? 0 : count.intValue());
        }

        return null;
    }

    public Integer getNextNumber(Long collectionId) {
        collectionRepo.increaseNumber(collectionId, 1);
        return collectionRepo.getCurrentNumber(collectionId).orElse(0);
    }

    public Asset createAsset(Collection collection, User user, Long orderId, BigDecimal price, String operation) {
        Integer number = null;
        Asset asset = Asset.create(collection, user);
        //主图根据序号生成
        if (StringUtils.isNotEmpty(collection.getPicPattern())) {
            //随机提取序号
            number = drawNumberFromPool(collection.getId());
            if (number == -1) {
                log.error("抽取资产编号失败，collectionId：{}", collection.getId());
                throw new BusinessException("资产编号抽取失败");
            }
            asset.setNumber(number);
            FileObject fileObject = parsePicPattern(collection.getPicPattern(), number, collection.getName());
            asset.setPic(Arrays.asList(fileObject));
            //组合名称
            asset.setComposeName(produceComposeName(asset));
        } else {
            number = getNextNumber(collection.getId());
        }

        asset.setTokenId(TokenUtils.genTokenId());
        asset.setNumber(number);
        asset.setOrderId(orderId);
        asset.setPrice(price);
        assetRepo.saveAndFlush(asset);

        tokenHistoryRepo.save(TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(collection.getMinter())
                .fromUserId(collection.getMinterId())
                .fromAvatar(collection.getMinterAvatar())
                .toUser(user.getNickname())
                .toUserId(user.getId())
                .toAvatar(user.getAvatar())
                .operation(operation)
                .price(price)
                .projectId(asset.getProjectId())
                .assetId(asset.getId())
                .orderId(orderId)
                .build());

        try {
            //需要延迟处理，防止数据库记录还未保存成功，队列就开始处理了,延时5s发送
            Message<?> message = MessageBuilder.withPayload(asset.getId()).build();
            rocketMQTemplate.syncSend(generalProperties.getMintTopic(), message, 8000L, 2);
        } catch (Exception e) {
            log.error("铸造任务消息发送失败", e);
        }

        //如果这个藏品有优先购买权限的话，生成一个优先购买凭证丢数据库
        addPriorityDocument(user.getId(), asset.getCollectionId(), asset, true, null, user.getPhone(), collection.getProjectId());
        return asset;
    }

    public Asset createAsset(Collection collection, BlindBoxItem winItem, User user, Long orderId, BigDecimal price, String operation) {
        Integer number = null;
        Asset asset = Asset.create(collection, winItem, user);
        //主图根据序号生成
        if (StringUtils.isNotEmpty(winItem.getPicPattern())) {
            //随机提取序号
            number = drawNumberFromPool(winItem.getCollectionId());
            if (number == -1) {
                log.error("抽取资产编号失败，collectionId：{}", winItem.getCollectionId());
                throw new BusinessException("资产编号抽取失败");
            }
            asset.setNumber(number);
            FileObject fileObject = parsePicPattern(winItem.getPicPattern(), number, winItem.getName());
            asset.setPic(Arrays.asList(fileObject));
            //组合名称
            asset.setComposeName(produceComposeName(asset));
        } else {
            number = getNextNumber(winItem.getCollectionId());
        }

        asset.setTokenId(TokenUtils.genTokenId());
        asset.setNumber(number);
        asset.setOrderId(orderId);
        asset.setPrice(price);
        assetRepo.saveAndFlush(asset);

        tokenHistoryRepo.save(TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(winItem.getMinter())
                .fromUserId(winItem.getMinterId())
                .fromAvatar(winItem.getMinterAvatar())
                .toUser(user.getNickname())
                .toUserId(user.getId())
                .toAvatar(user.getAvatar())
                .operation(operation)
                .price(price)
                .projectId(asset.getProjectId())
                .assetId(asset.getId())
                .orderId(orderId)
                .build());
        try {
            //需要延迟处理，防止数据库记录还未保存成功，队列就开始处理了,延时5s发送
            Message<?> message = MessageBuilder.withPayload(asset.getId()).build();
            rocketMQTemplate.syncSend(generalProperties.getMintTopic(), message, 8000L, 2);
        } catch (Exception e) {
            log.error("铸造任务消息发送失败", e);
        }

        //如果这个藏品有优先购买权限的话，生成一个优先购买凭证丢数据库
        addPriorityDocument(user.getId(), asset.getCollectionId(), asset, true, null, user.getPhone(), winItem.getProjectId());
        return asset;
    }

    /**
     * 如果这个藏品有优先购买权限的话，生成一个优先购买凭证丢数据库
     *
     * @param collectionId
     * @param asset
     * @param isNew        true购买，空投  false 转让、赠送
     */
    private void addPriorityDocument(Long userId, Long collectionId, Asset asset, boolean isNew, Long assetId, String phone, int projectId) {
        List<ActivityPriorityDocument> activityPriorityDocumentList = activityPriorityDocumentRepo.findByCollectionId(collectionId);
        if (activityPriorityDocumentList.size() == 0) return;
        List<UserWareHouse> list = new ArrayList<>();
        if (isNew) {
            activityPriorityDocumentList.forEach(document -> list.add(UserWareHouse.builder()
                    .assetId(asset.getId())
                    .userId(userId)
                    .disabled(true)
                    .projectId(projectId)
                    .itemId(document.ItemId)
                    .itemName(ItemType.getValue(document.ItemId))
                    .text(document.getText())
                    .phone(phone)
                    .advancePurchaseCount(document.getAdvancePurchaseCount())
                    .minterId(document.getMinterId())
                    .overdueTime(document.getOverdueTime())
                    .advancePurchaseTime(document.getAdvancePurchaseTime())
                    .targetCollectionId(document.getTargetCollectionId())
                    .activityPriorityDocumentId(document.getId())
                    .targetCollectionIdList(document.getTargetCollectionIdList()).build()));
        } else {
            //作废原先藏品的优先购
            List<UserWareHouse> acList = userWareHouseRepo.findByAssetId(asset.getId());
            for (UserWareHouse userWareHouse : acList) {
                userWareHouse.setDisabled(false);
                userWareHouse.setDel(true);
            }
            //生成新藏品的优先购
            acList.forEach(ac -> list.add(UserWareHouse.builder()
                    .assetId(assetId)
                    .userId(userId)
                    .phone(phone)
                    .projectId(projectId)
                    .itemId(ac.getItemId())
                    .disabled(true)
                    .itemName(ItemType.getValue(ac.getItemId()))
                    .text(ac.getText())
                    .advancePurchaseCount(ac.getAdvancePurchaseCount())
                    .advancePurchaseTime(ac.getAdvancePurchaseTime())
                    .targetCollectionId(ac.getTargetCollectionId())
                    .minterId(ac.getMinterId())
                    .overdueTime(ac.getOverdueTime())
                    .activityPriorityDocumentId(ac.getActivityPriorityDocumentId())
                    .targetCollectionIdList(ac.getTargetCollectionIdList()).build()));
        }
        userWareHouseRepo.saveAll(list);
    }

    /**
     * 展示
     */
    @RedisLockable(prefix = RedisKeys.OPERATION_ASSET_LOCK, key = "#userId + '_' + #assetId")
    @Transactional
    public void publicShow(Long userId, Long assetId) {
        Asset asset = assetRepo.findByIdForUpdate(assetId).orElseThrow(new BusinessException("资产无记录"));
        //上架检查
        this.onShelfCheck(asset);

        if (!asset.getUserId().equals(userId)) {
            throw new BusinessException("此藏品不属于你");
        }
        if (asset.isPublicShow()) {
            return;
        }
        if (asset.getStatus() != AssetStatus.NORMAL) {
            throw new BusinessException("当前状态不可展示");
        }
        User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
        Collection collection = Collection.builder()
                .name(asset.getName())
                .pic(asset.getPic())
                .minter(asset.getMinter())
                .minterId(asset.getMinterId())
                .minterAvatar(asset.getMinterAvatar())
                .owner(owner.getNickname())
                .ownerId(owner.getId())
                .ownerAvatar(owner.getAvatar())
                .detail(asset.getDetail())
                .type(CollectionType.DEFAULT)
                .source(CollectionSource.TRANSFER)
                .sale(0)
                .stock(1)
                .total(1)
                .onShelf(true)
                .salable(false)
                .price(BigDecimal.valueOf(0))
                .properties(asset.getProperties())
                .canResale(asset.isCanResale())
                .royalties(asset.getRoyalties())
                .serviceCharge(asset.getServiceCharge())
                .assetId(assetId)
                .number(asset.getNumber())
                .projectId(asset.getProjectId())
                .build();
        collectionRepo.save(collection);
        asset.setPublicShow(true);
        asset.setPublicCollectionId(collection.getId());
        assetRepo.save(asset);
    }

    /**
     * 寄售上架
     *
     * @param assetId
     * @param price
     */
    @RedisLockable(prefix = RedisKeys.OPERATION_ASSET_LOCK, key = "#userId + '_' + #assetId")
    @Transactional
    public Asset consignment(Long userId, Long assetId, BigDecimal price) {
        Asset asset = assetRepo.findByIdForUpdate(assetId).orElseThrow(new BusinessException("指定资产未找到"));

        if (!asset.getUserId().equals(userId)) {
            throw new BusinessException("此藏品不属于你");
        }

        //上架检查
        this.onShelfCheck(asset);

        int holdDays = sysConfigService.getInt(SysConstants.HOLD_DAYS);
        if (ChronoUnit.DAYS.between(asset.getCreatedAt(), LocalDateTime.now()) < holdDays) {
            throw new BusinessException("需持有满" + holdDays + "天才能寄售上架");
        }

        User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
        boolean isBind = uniformPayUtil.isBindBankCard(owner.getId());
        if (!isBind) {
            throw new BusinessException("请先绑定银行卡");
        }

        if (asset.isConsignment()) {
            throw new BusinessException("已寄售，请勿重新操作");
        }
        if (asset.getStatus() != AssetStatus.NORMAL) {
            throw new BusinessException("当前状态不可寄售");
        }

        if (asset.isPublicShow()) {
            asset = cancelPublic(userId, assetId);
        }

        Collection optionalCollection = null;
        if(asset.getCollectionId() != null){
            optionalCollection = collectionRepo.findById(asset.getCollectionId()).orElse(null);
        }


        Collection collection = Collection.builder()
                .name(asset.getName())
                .pic(asset.getPic())
                .minter(asset.getMinter())
                .minterId(asset.getMinterId())
                .minterAvatar(asset.getMinterAvatar())
                .owner(owner.getNickname())
                .ownerId(owner.getId())
                .ownerAvatar(owner.getAvatar())
                .detail(asset.getDetail())
                .type(optionalCollection == null? null:optionalCollection.getType())
                .source(CollectionSource.TRANSFER)
                .sale(0)
                .stock(1)
                .total(1)
                .onShelf(true)
                .salable(true)
                .price(price)
                .properties(asset.getProperties())
                .canResale(asset.isCanResale())
                .royalties(asset.getRoyalties())
                .serviceCharge(asset.getServiceCharge())
                .assetId(assetId)
                .number(asset.getNumber())
                .projectId(asset.getProjectId())
                .collectionCategory(optionalCollection == null ? null : optionalCollection.getCollectionCategory())
                .categories(asset.getCategories())
                .build();
        collectionRepo.save(collection);
        asset.setPublicShow(true);
        asset.setConsignment(true);
        asset.setPublicCollectionId(collection.getId());
        asset.setSellPrice(price);
        assetRepo.save(asset);
        //如果有优先购凭证先禁用
        List<UserWareHouse> userWareHouses = userWareHouseRepo.findByAssetId(asset.getId());
        if (userWareHouses.size() > 0) {
            for (UserWareHouse userWareHouse : userWareHouses) {
                userWareHouse.setDisabled(false);
            }
            userWareHouseRepo.saveAll(userWareHouses);
        }

        //限制售卖处理
        checkLimitSellPriceData(asset);

        return asset;
    }


    /**
     * 取消上架
     *
     * @param assetId
     */
    @RedisLockable(prefix = RedisKeys.OPERATION_ASSET_LOCK, key = "#userId + '_' + #assetId")
    @Transactional
    public Asset cancelConsignment(Long userId, Long assetId) {
        Asset asset = assetRepo.findByIdForUpdate(assetId).orElseThrow(new BusinessException("资产无记录"));
        if (!asset.getUserId().equals(userId)) {
            throw new BusinessException("此藏品不属于你");
        }
        if (asset.getPublicCollectionId() != null) {
            List<Order> orders = orderRepo.findByCollectionId(asset.getPublicCollectionId());
            if (orders.stream().anyMatch(o -> o.getStatus() != OrderStatus.CANCELLED)) {
                throw new BusinessException("已有订单不可取消");
            }
            collectionRepo.findById(asset.getPublicCollectionId())
                    .ifPresent(collection -> {
                        collection.setSalable(false);
                        collectionRepo.save(collection);
                    });
        }
        asset.setConsignment(false);
        assetRepo.save(asset);
        //如果有优先购凭证恢复使用
        List<UserWareHouse> userWareHouses = userWareHouseRepo.findByAssetId(asset.getId());
        if (userWareHouses.size() > 0) {
            for (UserWareHouse userWareHouse : userWareHouses) {
                userWareHouse.setDisabled(true);
            }
            userWareHouseRepo.saveAll(userWareHouses);
        }

        //限制售卖处理
        checkLimitSellPriceData(asset);
        return asset;
    }

    /**
     * 取消展示
     *
     * @param assetId
     */
    @RedisLockable(prefix = RedisKeys.OPERATION_ASSET_LOCK, key = "#userId + '_' + #assetId")
    @Transactional
    public Asset cancelPublic(Long userId, Long assetId) {
        Asset asset = assetRepo.findByIdForUpdate(assetId).orElseThrow(new BusinessException("资产无记录"));
        if (!asset.getUserId().equals(userId)) {
            throw new BusinessException("此藏品不属于你");
        }

        if (!asset.isPublicShow()) {
            return asset;
        }
        if (asset.isConsignment()) {
            asset = cancelConsignment(userId, assetId);
        }

        //删除藏品
        if (asset.getPublicCollectionId() != null) {
            Collection collection = collectionRepo.findById(asset.getPublicCollectionId())
                    .orElseThrow(new BusinessException("无展示记录"));
            collectionRepo.delete(collection);
        } else {
            //通过assetId关联删除
            List<Long> collectionIds = collectionRepo.findAllByAssetId(asset.getId());
            if (CollectionUtils.isNotEmpty(collectionIds)) {
                log.info("取消展示删除collection {}", collectionIds);
                collectionRepo.deleteAllByIdIn(collectionIds);
            }

        }


        asset.setPublicShow(false);
        asset.setPublicCollectionId(null);
        assetRepo.save(asset);

        return asset;
    }


    public void usePrivilege(Long assetId, Long privilegeId) {
        Asset asset = assetRepo.findById(assetId).orElseThrow(new BusinessException("资产无记录"));
        asset.getPrivileges().stream().filter(p -> p.getId().equals(privilegeId)).forEach(p -> {
            p.setOpened(true);
            p.setOpenTime(LocalDateTime.now());
            p.setOpenedBy(SecurityUtils.getAuthenticatedUser().getId());
        });
        assetRepo.save(asset);
    }

    /**
     * 转让或转赠执行
     *
     * @param asset
     * @param price
     * @param toUser
     * @param reason
     * @param orderId
     */
    @Transactional
    public void transfer(Asset asset, BigDecimal price, User toUser, String reason, Long orderId) {
        log.info("转让藏品 fromAssetId={} toUser={}", asset.getId(), toUser.getId());
        //防止重复转让，检查资产重复数量
        Integer holdCount = assetRepo.countAssetByTokenId(asset.getTokenId());
        if (holdCount > 1) {
            throw new BusinessException("持有的资产不是唯一的，无法进行" + reason);
        }

        Asset newAsset = new Asset();
        BeanUtils.copyProperties(asset, newAsset);
        newAsset.setId(null);
        newAsset.setUserId(toUser.getId());
        newAsset.setOwner(toUser.getNickname());
        newAsset.setOwnerId(toUser.getId());
        newAsset.setOwnerAvatar(toUser.getAvatar());
        newAsset.setPublicShow(false);
        newAsset.setConsignment(false);
        newAsset.setPublicCollectionId(null);
        newAsset.setStatus(AssetStatus.NORMAL);
        newAsset.setPrice(price);
        newAsset.setSellPrice(null);
        newAsset.setOrderId(orderId);
        newAsset.setFromAssetId(asset.getId());
        assetRepo.save(newAsset);

        tokenHistoryRepo.save(TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(asset.getOwner())
                .fromUserId(asset.getOwnerId())
                .fromAvatar(asset.getOwnerAvatar())
                .toUser(toUser.getNickname())
                .toUserId(toUser.getId())
                .toAvatar(toUser.getAvatar())
                .operation(reason)
                .price("转赠".equals(reason) ? null : price)
                .projectId(asset.getProjectId())
                .assetId(newAsset.getId())
                .orderId(orderId)
                .build());

        asset.setPublicShow(false);
        asset.setConsignment(false);
        asset.setPublicCollectionId(null);
        asset.setStatus("转赠".equals(reason) ? AssetStatus.GIFTED : AssetStatus.TRANSFERRED);
        asset.setOwner(toUser.getNickname());
        asset.setOwnerId(toUser.getId());
        asset.setOwnerAvatar(toUser.getAvatar());
        asset.setOutOrderId(orderId);
        asset.setOutTxHash(asset.getTxHash());
        asset.setOutTime(LocalDateTime.now());
        assetRepo.save(asset);

        if (orderId != null) {
            //数据库操作，不需要异步调用，直接修改
            Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
            order.setTxHash(asset.getTxHash());
            order.setGasUsed(asset.getGasUsed());
            order.setBlockNumber(asset.getBlockNumber());
            order.setStatus(OrderStatus.FINISH);
            orderRepo.save(order);
        }
        //如果这个藏品有优先购买权限的话，生成一个优先购买凭证丢数据库
        addPriorityDocument(toUser.getId(), asset.getCollectionId(), asset, false, newAsset.getId(), toUser.getPhone(), asset.getProjectId());
    }

    public List<TokenHistory> tokenHistory(String tokenId, Long assetId) {
        if (tokenId == null) {
            if (assetId == null) return new ArrayList<>();
            tokenId = assetRepo.findById(assetId).map(Asset::getTokenId).orElse(null);
        }
        if (tokenId == null) return new ArrayList<>();
        return tokenHistoryRepo.findByTokenIdOrderByCreatedAtDesc(tokenId);
    }


    public void setHistory() {
        List<Asset> assets = assetRepo.findByCreatedAtBefore(LocalDateTime.of(2021, 11, 22, 23, 59, 59));
        assets.parallelStream().forEach(asset -> {
            try {
                User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException(""));
                Order order = orderRepo.findById(asset.getOrderId()).orElseThrow(new BusinessException(""));
                TokenHistory t = TokenHistory.builder()
                        .tokenId(asset.getTokenId())
                        .fromUser(asset.getMinter())
                        .fromUserId(asset.getMinterId())
                        .fromAvatar(asset.getMinterAvatar())
                        .toUser(owner.getNickname())
                        .toUserId(owner.getId())
                        .toAvatar(owner.getAvatar())
                        .operation("出售")
                        .price(order.getPrice())
                        .projectId(asset.getProjectId())
                        .assetId(asset.getId())
                        .build();
                t.setCreatedAt(asset.getCreatedAt());
                tokenHistoryRepo.save(t);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 交易历史
     *
     * @param userId
     * @param projectId
     * @param type
     * @param pageable
     * @return
     */
    public Page<UserHistory> userHistory(Long userId, int projectId, Integer type, Pageable pageable) {
        //特殊处理，显示全部
        if(type != null && type >= 8){
            type = -1;
        }
        //操作类型
        OperationType operationType = null;
        if (type != null) {
            operationType = OperationType.getValue(type);
        }

        Page<TokenHistory> page;
        LocalDateTime createdAt = LocalDateTime.now().plusMonths(-120);//不限制
        if (operationType != null) {
            if (type == 0) {
                //买入
                page = tokenHistoryRepo.userHistoryAndProjectIdbuy(userId, projectId, OperationType.TRANSFER.getDescription(), OperationType.SELL.getDescription(), createdAt, pageable);
            } else if (type == 1) {
                //卖出
                page = tokenHistoryRepo.userHistoryAndProjectIdsale(userId, projectId, operationType.getDescription(), createdAt, pageable);
            } else {
                //2赠送  3兑换  4 空投  5惊喜盒子 6 盲合打开 7积分抽奖
                page = tokenHistoryRepo.userHistoryAndProjectIdoperation(userId, projectId, operationType.getDescription(), createdAt, pageable);
            }
        } else {
            //全部
            page = tokenHistoryRepo.userHistoryAndProjectId(userId, projectId, createdAt, pageable);
        }
        return page.map(tokenHistory -> {
            UserHistory userHistory = new UserHistory();
            BeanUtils.copyProperties(tokenHistory, userHistory);
            List<Asset> assets = assetRepo.findByTokenIdIn(List.of(tokenHistory.getTokenId()));
            Optional<Asset> asset0 = assets.stream().filter(a -> Objects.equals(a.getPrice(), tokenHistory.getPrice()) && tokenHistory.getToUserId().equals(a.getUserId())).findAny();
            Optional<Asset> asset1 = assets.stream().filter(a -> Objects.equals(a.getPrice(), tokenHistory.getPrice())).findAny();
            Optional<Asset> asset2 = assets.stream().filter(a -> Objects.equals(a.getId(), tokenHistory.getAssetId())).findAny();
            Optional<Asset> asset = asset2.isPresent() ? asset2 : asset0.isPresent() ? asset0 : asset1;
            userHistory.setAssetName(asset.map(Asset::getName).orElse(null));
            userHistory.setPic(asset.map(Asset::getPic).orElse(new ArrayList<>()));
            Long orderId = tokenHistory.getOrderId();
            if (orderId == null && asset.isPresent()) {
                orderId = asset.get().getOrderId();
            }
            if (orderId != null) {
                Optional<Order> order = orderRepo.findById(orderId);
                if (order.isPresent()) {
                    userHistory.setPrice(order.get().getPrice());//卖出
                    userHistory.setTotalPrice(order.get().getTotalPrice());//支付
                    userHistory.setGasPrice(order.get().getGasPrice());//
                    userHistory.setRoyalties(order.get().getRoyalties() + order.get().getServiceCharge());
                    userHistory.setArrivalAmount(order.get().getPrice().subtract(order.get().getPrice().multiply(BigDecimal.valueOf(userHistory.getRoyalties() / 100.0))).setScale(2, RoundingMode.HALF_UP));
                    userHistory.setPayMethod(order.get().getPayMethod());
                }
            }
            OperationType useroperationType = OperationType.getValue(tokenHistory.getOperation());
            if (useroperationType == null) {
                return userHistory;
            }
            userHistory.setDescription(useroperationType.getDescription());
            switch (useroperationType) {
                case SURPRISE_BOX:
                    userHistory.setDescription("获得");
                    break;
                case blind_box_open:
                    userHistory.setDescription("盲盒");
                    break;
                case SELL:
                    userHistory.setDescription("购买");//官方买入
                    break;
                case TRANSFER:
                    userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "购买" : "售出");//二手市场交易
                    break;
                case EXCHANGE:
                    userHistory.setDescription("兑换");
                    break;
                case AIRDROP:
                    userHistory.setDescription("空投");
                    break;
                case give:
                    userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "获得" : "赠送");
                    long blackUserId = sysConfigService.getBigDecimal(SysConstants.BLACK_USER_ID).longValue();
                    if (blackUserId == 0) {
                        blackUserId = Long.parseLong(Constants.DEFAULT_BLACK_USER);
                    }
                    if (userHistory.getToUserId().equals(blackUserId)) {
                        Optional<Asset> asset3 = assets.stream().filter(a -> a.getId().equals(userHistory.getAssetId())).findAny();
                        if (asset3.isPresent()) {
                            userHistory.setAssetName(asset3.get().getName());
                            userHistory.setPic(asset3.get().getPic());
                        }
                        userHistory.setDescription("销毁");
                    }
                    break;
            }
            return userHistory;
        });
    }


    public Map<User, Integer> holdQuery(List<String> names, LocalDateTime startTime, LocalDateTime endTime) {
        Map<User, Integer> result = new HashMap<>();
        userRepo.findAll().stream().parallel().forEach(user -> {
            List<Asset> assets = assetRepo.findByUserId(user.getId());
            assets = assets.stream().filter(a -> names.stream().anyMatch(n -> n.equals(a.getName())))
                    .collect(Collectors.toList());
            if (assets.size() < names.size()) {
                return;
            }
            assets = assets.stream().filter(a -> a.getCreatedAt().isBefore(startTime))
                    .collect(Collectors.toList());
            if (assets.size() < names.size()) {
                return;
            }
            assets = assets.stream().filter(a -> {
                        if (a.getStatus() != AssetStatus.GIFTED && a.getStatus() != AssetStatus.TRANSFERRED) {
                            return true;
                        } else {
                            Asset a1 = assetRepo.findFirstByTokenIdAndCreatedAtAfterOrderByCreatedAt(a.getTokenId(), a.getCreatedAt());
                            return a1 != null && a.getCreatedAt().isAfter(endTime);
                        }
                    })
                    .collect(Collectors.toList());
            boolean flag = true;
            Map<String, Integer> map = new HashMap<>();
            for (String name : names) {
                int c = (int) assets.stream().filter(a -> name.equals(a.getName())).count();
                map.put(name, c);
                flag = flag && (c > 0);
            }
            if (flag) {
                result.put(user, map.values().stream().mapToInt(i -> i).min().orElse(0));
            }
        });
        return result;
    }

    public Map<User, Integer> holdQuery1(List<Long> names, LocalDateTime startTime, LocalDateTime endTime) {
        Map<User, Integer> result = new HashMap<>();
        userRepo.findAll().stream().parallel().forEach(user -> {
            List<Asset> assets = assetRepo.findByUserId(user.getId());
            assets = assets.stream().filter(a -> names.stream().anyMatch(n -> n.equals(a.getCollectionId())))
                    .collect(Collectors.toList());
            if (assets.size() < names.size()) {
                return;
            }
            assets = assets.stream().filter(a -> a.getCreatedAt().isBefore(startTime))
                    .collect(Collectors.toList());
            if (assets.size() < names.size()) {
                return;
            }
            assets = assets.stream().filter(a -> {
                        if (a.getStatus() != AssetStatus.GIFTED && a.getStatus() != AssetStatus.TRANSFERRED) {
                            return true;
                        } else {
                            Asset a1 = assetRepo.findFirstByTokenIdAndCreatedAtAfterOrderByCreatedAt(a.getTokenId(), a.getCreatedAt());
                            return a1 != null && a.getCreatedAt().isAfter(endTime);
                        }
                    })
                    .collect(Collectors.toList());
            boolean flag = true;
            Map<Long, Integer> map = new HashMap<>();
            for (Long name : names) {
                int c = (int) assets.stream().filter(a -> name.equals(a.getCollectionId())).count();
                map.put(name, c);
                flag = flag && (c > 0);
            }
            if (flag) {
                result.put(user, map.values().stream().mapToInt(i -> i).min().orElse(0));
            }
        });
        return result;
    }

    public void transferCDN() throws ExecutionException, InterruptedException {
        ForkJoinPool customThreadPool = new ForkJoinPool(100);
        customThreadPool.submit(() -> {
            collectionRepo.selectResource().parallelStream().forEach(list -> {
                for (int i = 0; i < list.size(); i++) {
                    list.set(i, replaceCDN(list.get(i)));
                }
                collectionRepo.updateCDN(Long.parseLong(list.get(0)),
                        list.get(1),
                        list.get(2),
                        list.get(3),
                        list.get(4),
                        list.get(5));
            });

            assetRepo.selectResource().parallelStream().forEach(list -> {
                for (int i = 0; i < list.size(); i++) {
                    list.set(i, replaceCDN(list.get(i)));
                }
                assetRepo.updateCDN(Long.parseLong(list.get(0)),
                        list.get(1),
                        list.get(2),
                        list.get(3),
                        list.get(4),
                        list.get(5));
            });
        }).get();
    }

    public String replaceCDN(String url) {
        if (url == null) return null;
        return url.replaceAll("https://9space-2021\\.oss-cn-shenzhen\\.aliyuncs\\.com",
                "https://cosmoscdn.9space.vip");
    }

    /**
     * 上架检查
     *
     * @param asset
     */
    public void onShelfCheck(Asset asset) {
        if (asset.getName().contains("鬼卒-")
                && LocalDate.now().equals(LocalDate.of(2022, 3, 22))) {
            throw new BusinessException("此藏品当前无法上架");
        }

        if(asset.getCollectionId() != null) {
            Optional<Collection> assetCollection = collectionRepo.findById(asset.getCollectionId());
            if (assetCollection.isPresent() && assetCollection.get().getStartTime() != null && assetCollection.get().getStartTime().isAfter(LocalDateTime.now())) {
                throw new BusinessException("当前还未开售");
            }
            if (asset.getBlindBoxId() != null) {
                assetCollection = collectionRepo.findById(asset.getBlindBoxId());
                if (assetCollection.isPresent() && assetCollection.get().getStartTime() != null && assetCollection.get().getStartTime().isAfter(LocalDateTime.now())) {
                    throw new BusinessException("当前还未开售");
                }
            }

            if (activityCollectionService.isOnActivity(asset.getCollectionId())) {
                throw new BusinessException("该藏品在活动合成期间不可寄售，请在合成活动结束后上架寄售");
            }

        }

        String values = sysConfigService.getStringDefault(SysConstants.ASSET_SHELF_CHECK);
        if (StringUtils.isNotBlank(values)) {
            String[] limitStrs = values.split(",");
            for (String val : limitStrs) {
                if (asset.getName().contains(val)) {
                    throw new BusinessException("此藏品当前无法上架~");
                }
            }
        }


        //防止重复转让，检查资产重复数量
        Integer holdCount = assetRepo.countAssetByTokenId(asset.getTokenId());
        if (holdCount > 1) {
            log.error("重复资产持有错误, assetId:{}, tokenId:{} ", asset.getId(), asset.getTokenId());
            throw new BusinessException("持有的资产重复，无法上架或转赠");
        }


    }

    /**
     * 解析主图地址
     *
     * @param picPattern
     * @param number
     * @return
     */
    public FileObject parsePicPattern(String picPattern, Integer number, String name) {
        //String newPic = picPattern.trim().replace("{}", String.format("%03d", number));
        String newPic = picPattern.trim().replace("{}", String.format("%d", number));
        String type = URLConnection.guessContentTypeFromName(newPic);
        String suffix = FilenameUtils.getExtension(newPic);
        FileObject fileObject = new FileObject();
        String newName = name + "-" + number + "." + suffix;
        fileObject.setName(newName);
        fileObject.setType(type);
        fileObject.setUrl(newPic);
        return fileObject;
    }

    /**
     * 抽取藏品的资产编号
     *
     * @return
     */
    public Integer drawNumberFromPool(Long collectionId) {
        String redisKey = RedisKeys.COLLECTION_NUMBER_POOL + collectionId;
        Object number = -1;
        BoundListOperations<String, Object> ops = redisTemplate.boundListOps(redisKey);
        int size = ops.size().intValue();
        if (size > 0) {
            int index = RandomUtils.nextInt(0, size);
            number = ops.index(index);
            ops.remove(0, number);
        }
        log.info("成功抽取资产编号，藏品ID：{}， 资产编号：{}", collectionId, number);
        return Integer.valueOf(number.toString());
    }

    /**
     * 生成组合名称
     *
     * @param asset
     * @return
     */
    public String produceComposeName(Asset asset) {
        Integer number = asset.getNumber();
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, 2000);
        calendar.set(Calendar.DAY_OF_YEAR, number);
        String suffixName = DateUtil.format(calendar.getTime(), "MM月dd日");
        String composeName = asset.getName() + "-" + suffixName;
        return composeName;
    }

    /**
     * 初始化售卖价格限制数据
     *
     * @return
     */
    public Integer initLimitSellPriceData() {
        List<AssetSellLimit> limitList = assetSellLimitRepo.findAll();
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        AtomicInteger changeCount = new AtomicInteger();
        //应用规则
        limitList.forEach(item -> {
            List<Asset> limitAssets = assetRepo.findLimitPriceAsset(item.getCollectionId(), item.getMinPrice());
            //初始化藏品的限制
            limitAssets.forEach(asset -> {
                String key = RedisKeys.ASSET_SHELL_LIMIT_KEY + asset.getId();
                ops.set(key, asset.getSellPrice().toPlainString());

                //设置失效时间
                if (item.getExpireSeconds() != null && item.getExpireSeconds() > 0) {
                    redisTemplate.expire(key, item.getExpireSeconds(), TimeUnit.SECONDS);
                }

                if (item.getExpireDateTime() != null) {
                    redisTemplate.expireAt(key, item.getExpireDateTime().atZone(ZoneId.systemDefault()).toInstant());
                }

                changeCount.incrementAndGet();

            });

        });


        return changeCount.get();
    }

    /**
     * 添加限制售卖信息
     */
    public void checkLimitSellPriceData(Asset asset) {
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String key = RedisKeys.ASSET_SHELL_LIMIT_KEY + asset.getId();
        if (asset.getPublicCollectionId() == null) {
            return;
        }

        List<AssetSellLimit> limitList = assetSellLimitRepo.findByCollectionId(asset.getPublicCollectionId());
        limitList.forEach(item -> {
            if (asset.getSellPrice() != null && asset.getSellPrice().compareTo(item.getMinPrice()) <= 0) {
                ops.set(key, asset.getSellPrice().toPlainString());
                //设置失效时间
                if (item.getExpireSeconds() != null && item.getExpireSeconds() > 0) {
                    redisTemplate.expire(key, item.getExpireSeconds(), TimeUnit.SECONDS);
                }

                if (item.getExpireDateTime() != null) {
                    redisTemplate.expireAt(key, item.getExpireDateTime().atZone(ZoneId.systemDefault()).toInstant());
                }

            } else {
                redisTemplate.delete(key);
            }

        });


    }



    /**
     * 修改资产假数据,非事务处理
     *
     * @param asset
     */
    @Transactional(Transactional.TxType.NEVER)
    public boolean mockLimitData(Asset asset) {
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String key = RedisKeys.ASSET_SHELL_LIMIT_KEY + asset.getId();
        String oldValue = ops.get(key);
        if (oldValue != null) {
            asset.setStatus(AssetStatus.TRADING);
            return true;
        }

        return false;
    }


    /**
     * 查询某个用户卖出的总金额
     *
     * @return
     */
    public BigDecimal sellAmountFromUserId(Long userid) {
        return assetRepo.sellAmountFromUserId(userid);
    }

    @RedisLockable(prefix = RedisKeys.OPEN_BLIND_BOX_LOCK, key = "#assetId", expiration = 360)
    @Transactional
    public Asset variableAsset(Long assetId, User user) {
        //锁定用户持有资产
        Asset asset = assetRepo.findByIdForUpdate(assetId).orElseThrow(new BusinessException("资产不存在"));

        if (!asset.getUserId().equals(user.getId())) {
            throw new BusinessException("无权限");
        }
        if (asset.getStatus() != AssetStatus.NORMAL) {
            throw new BusinessException("当前状态不可打开");
        }
        if (asset.isConsignment()) {
            throw new BusinessException("请先取消寄售");
        }
        if (!asset.isVariableType()) {
            throw new BusinessException("资产不可打开");
        }

        //指定账户,黑洞账户
        long blackUserId = sysConfigService.getBigDecimal(SysConstants.BLACK_USER_ID).longValue();
        if (blackUserId == 0) {
            blackUserId = Long.parseLong(Constants.DEFAULT_BLACK_USER);
        }
        User newOwner = userRepo.findByIdAndDelFalse(blackUserId).orElseThrow(new BusinessException("管理员未配置目标管理账户，无法兑换"));
        //创建新藏品
        Collection collection = collectionRepo.findById(asset.getCollectionId()).orElseThrow(new BusinessException("藏品不存在"));
        if (collection.getType() != CollectionType.BLIND_BOX) {
            throw new BusinessException("不是盲盒，不能打开");
        }
        //把当前资产转赠到黑洞账户
        asset.setVariableType(false);
        transfer(asset, asset.getPrice(), newOwner, OperationType.give.getDescription(), null);
        BlindBoxItem winItem = collectionService.draw(collection.getId());
        return createAsset(collection, winItem, user, null, asset.getPrice(), OperationType.blind_box_open.getDescription());
    }


    public Map<String, Object> userHoldNum(Long id, int projectId) {
        return assetRepo.userHoldNum(id, projectId);
    }


    /**
     * 查询持有指定藏品期间卖出产品税率减免查询
     *
     * @param collectionId key 用户ID value退税金额
     */
    public List<DrawbackDTO> findAssetCollectionId(Long collectionId,LocalDateTime limitStartTime ,LocalDateTime limitEndTime) {
        //找出持有过该藏品的玩家
        List<Asset> assets = assetRepo.findAssetCollectionId(collectionId);
        List<HoldAssetDate> list = new ArrayList<>();
        assets.forEach(e -> list.add(new HoldAssetDate(e.getUserId(), e.getCreatedAt(), e.getOutTime())));
        Map<Long, List<HoldAssetDate>> holdAssetDateMap = list.stream().collect(Collectors.groupingBy(HoldAssetDate::getUserId));

        //找出该玩家持有指定藏品的期间所拥有过的所有藏品
        Map<Long, List<Asset>> mapSet = new HashMap<>();

        Set<Long> assetids = new TreeSet<>();
        holdAssetDateMap.forEach((userId, holdAssetDates) -> {
            List<Asset> assetList = new ArrayList<>();
            for (HoldAssetDate e: holdAssetDates){
                //这个特殊藏品持有时间在指定时间内
                if (e.endTime.isAfter(limitStartTime)){
                    LocalDateTime start = limitStartTime.isAfter(e.startTime)?limitStartTime:e.startTime;//谁大用谁
                    LocalDateTime end = limitEndTime.isBefore(e.endTime)?limitEndTime:e.endTime;//谁小用谁
                    List<Asset>  userAssets ;
                    if (assetids.size()>0){
                      userAssets = assetRepo.findUserAssetOutTime(userId, start, end,assetids);
                   }else {
                      userAssets = assetRepo.findUserAssetOutTime(userId, start, end, new HashSet<>(Arrays.asList(-1L)));
                   }
                    userAssets.forEach(userAsset -> {
                        if (!assetids.contains(userAsset.getId())){
                            assetList.add(userAsset);
                            assetids.add(userAsset.getId());
                        }
                    });
                }
            }
            assetids.clear();
            if (assetList.size()>0){
                mapSet.put(userId, assetList);
            }
        });
        //根据用户持有过的产品找出已售订单
        Map<Long, List<Order>> mapUserOrder = new HashMap<>();
        mapSet.forEach((userId, asset) -> {
            if (asset.size() > 0) {
                List<Order> orderList = orderRepo.findByAssetIdInAndStatusIn(asset.stream().map(Asset::getId)
                                .collect(Collectors.toSet()),
                        Arrays.asList(OrderStatus.FINISH, OrderStatus.PROCESSING));
                mapUserOrder.put(userId, orderList);
            }
        });

        Map<Long, String> userPhones = userRepo.findAllById(mapSet.keySet()).stream()
                .collect(Collectors.toMap(User::getId, User::getPhone));
        //计算用户退税金额
        Map<Long, DrawbackDTO> userRefundMap = new HashMap<>();
        mapUserOrder.forEach((usrId, orders) -> {
            List<HoldAssetDate> list1 = holdAssetDateMap.get(usrId);
            orders.forEach(order -> {
                int count = 0;
                for (HoldAssetDate holdAssetDate : list1) {
                    if (order.getCreatedAt().isBefore(holdAssetDate.endTime)
                            && order.getCreatedAt().isAfter(holdAssetDate.startTime)) {
                        count++;
                    }
                }
                //每持有一个指定藏品面试1% ，最多叠加三次
                count = Math.min(count, 3);
                int finalCount = count;
                userRefundMap.compute(usrId, (key, drawbackDTO) -> {
                    //单笔退税金
                    BigDecimal scale = new BigDecimal(finalCount).divide(new BigDecimal(100));
                    BigDecimal amount = order.getPrice().multiply(scale).setScale(2, RoundingMode.DOWN);
                    if (drawbackDTO == null) {
                        drawbackDTO = new DrawbackDTO(key, userPhones.get(key), BigDecimal.ZERO);
                        drawbackDTO.setOrderNum(orders.size());
                        drawbackDTO.setBackNum(0);
                    }

                    drawbackDTO.setTotalPrice(drawbackDTO.getTotalPrice().add(order.getPrice()));
                    drawbackDTO.setAmount(drawbackDTO.getAmount().add(amount));
                    if(finalCount > 0){
                        //累计退税的数量
                        drawbackDTO.setBackNum(drawbackDTO.getBackNum() + 1);

                        //时间范围
                        if(drawbackDTO.getMinOrderTime() == null || drawbackDTO.getMinOrderTime().compareTo(order.getCreatedAt()) > 0){
                            drawbackDTO.setMinOrderTime(order.getCreatedAt());
                        }

                        if(drawbackDTO.getMaxOrderTime() == null || drawbackDTO.getMaxOrderTime().compareTo(order.getCreatedAt()) < 0){
                            drawbackDTO.setMaxOrderTime(order.getCreatedAt());
                        }

                        //比例范围
                        if(drawbackDTO.getMinScale().compareTo(scale) > 0){
                            drawbackDTO.setMinScale(scale);
                        }

                        if(drawbackDTO.getMaxScale().compareTo(scale) < 0){
                            drawbackDTO.setMaxScale(scale);
                        }

                    }

                    return drawbackDTO;
                });
            });
        });

        List<DrawbackDTO> drawbackDTOList = new ArrayList<>(userRefundMap.values());
        return drawbackDTOList;
    }


}
