package com.izouma.nineth.service;

import cn.hutool.core.convert.Convert;
import com.fasterxml.jackson.annotation.JsonView;
import com.izouma.nineth.TokenHistory;
import com.izouma.nineth.config.GeneralProperties;
import com.izouma.nineth.domain.Collection;
import com.izouma.nineth.domain.*;
import com.izouma.nineth.dto.PageQuery;
import com.izouma.nineth.dto.PageWrapper;
import com.izouma.nineth.dto.UserHistory;
import com.izouma.nineth.enums.*;
import com.izouma.nineth.event.TransferAssetEvent;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.repo.*;
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.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;

@Service
@AllArgsConstructor
@Slf4j
public class AssetService {

    private AssetRepo               assetRepo;
    private UserRepo                userRepo;
    private CollectionRepo          collectionRepo;
    private ApplicationContext      applicationContext;
    private OrderRepo               orderRepo;
    private TokenHistoryRepo        tokenHistoryRepo;
    private SysConfigService        sysConfigService;
    private RocketMQTemplate        rocketMQTemplate;
    private GeneralProperties       generalProperties;
    private ShowroomRepo            showroomRepo;
    private ShowCollectionRepo      showCollectionRepo;
    private CollectionPrivilegeRepo collectionPrivilegeRepo;
    private PasswordEncoder         passwordEncoder;
    private MintActivityRepo        mintActivityRepo;

    public Page<Asset> all(PageQuery pageQuery) {
        Page<Asset> all = assetRepo
                .findAll(JpaUtils.toSpecification(pageQuery, Asset.class), JpaUtils.toPageRequest(pageQuery));
        Map<String, Object> query = pageQuery.getQuery();
        if (query.containsKey("userId")) {
            List<Long> orderId = orderRepo
                    .findAllByUserIdAndOpenedFalse(Convert.convert(Long.class, query.get("userId")));
            return all.map(asset -> {
                if (orderId.contains(asset.getOrderId())) {
                    asset.setOpened(false);
                }
                return asset;
            });
        }
        return all;
    }

    public Asset createAsset(Collection collection, User user, Long orderId, BigDecimal price, String type, Integer number) {
        Asset asset = Asset.create(collection, user);
        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(type)
                .price(price)
                .build());
        rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
        return asset;
    }

    public Asset createAsset(BlindBoxItem winItem, User user, Long orderId, BigDecimal price, String type,
                             Integer number, Integer holdDays) {
        Collection blindBox = collectionRepo.findDetailById(winItem.getBlindBoxId())
                .orElseThrow(new BusinessException("盲盒不存在"));
        Collection collection = collectionRepo.findDetailById(winItem.getCollectionId())
                .orElseThrow(new BusinessException("藏品不存在"));
        Asset asset = Asset.create(winItem, user, holdDays);
        asset.setTokenId(TokenUtils.genTokenId());
        asset.setNumber(number);
        asset.setOrderId(orderId);
        asset.setPrice(price);
        asset.setTags(new HashSet<>());
        if (blindBox.getTags() != null) {
            asset.getTags().addAll(blindBox.getTags());
        }
        if (collection.getTags() != null) {
            asset.getTags().addAll(collection.getTags());
        }
        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(type)
                .price(price)
                .build());
        rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
        return asset;
    }


    public void publicShow(Long id) {
        Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
            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(id)
                .number(asset.getNumber())
                .build();
        collectionRepo.save(collection);
        asset.setPublicShow(true);
        asset.setPublicCollectionId(collection.getId());
        assetRepo.save(asset);
    }

    public synchronized void consignment(Long id, BigDecimal price, String tradeCode) {
        Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
            throw new BusinessException("此藏品不属于你");
        }

        int holdDays;
        if (asset.getSource() == AssetSource.GIFT) {
            holdDays = sysConfigService.getInt("gift_days");
        } else {
            if (ObjectUtils.isEmpty(asset.getHoldDays())) {
                holdDays = sysConfigService.getInt("hold_days");
            } else {
                holdDays = asset.getHoldDays();
            }
        }

        if (ChronoUnit.DAYS.between(asset.getCreatedAt(), LocalDateTime.now()) < holdDays) {
            throw new BusinessException("需持有满" + holdDays + "天才能寄售上架");
        }
        User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
        if (!passwordEncoder.matches(tradeCode, owner.getTradeCode())) {
            throw new BusinessException("交易密码错误");
        }
//        if (StringUtils.isBlank(owner.getSettleAccountId())) {
//            throw new BusinessException("请先绑定银行卡");
//        }
        if (asset.isConsignment()) {
            throw new BusinessException("已寄售，请勿重新操作");
        }
        if (asset.getStatus() != AssetStatus.NORMAL) {
            throw new BusinessException("当前状态不可寄售");
        }
        if (asset.isPublicShow()) {
            cancelPublic(asset);
        }

        //寄售中的展厅需要先删除展厅
        if (CollectionType.SHOWROOM.equals(asset.getType())) {
            if (showroomRepo.findByAssetId(id).isPresent()) {
                throw 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(true)
                .price(price)
                .properties(asset.getProperties())
                .canResale(asset.isCanResale())
                .royalties(asset.getRoyalties())
                .serviceCharge(asset.getServiceCharge())
                .assetId(id)
                .number(asset.getNumber())
                .build();
        collectionRepo.save(collection);
        asset.setPublicShow(true);
        asset.setConsignment(true);
        asset.setPublicCollectionId(collection.getId());
        asset.setSellPrice(price);
        assetRepo.save(asset);
    }

    public void cancelConsignment(Long id) {
        Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
            throw new BusinessException("此藏品不属于你");
        }
        cancelConsignment(asset);
    }

    public void cancelConsignment(Asset asset) {
        if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
            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);
    }

    public void cancelPublic(Long id) {
        Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
            throw new BusinessException("此藏品不属于你");
        }
        cancelPublic(asset);
    }

    public void cancelPublic(Asset asset) {
        if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
            throw new BusinessException("此藏品不属于你");
        }
        if (!asset.isPublicShow()) {
            return;
        }
        if (asset.isConsignment()) {
            cancelConsignment(asset);
        }
        Collection collection = collectionRepo.findById(asset.getPublicCollectionId())
                .orElseThrow(new BusinessException("无展示记录"));
        collectionRepo.delete(collection);
        // 如果展厅有此藏品
        showCollectionRepo.deleteAllByCollectionId(asset.getPublicCollectionId());

        asset.setPublicShow(false);
        asset.setPublicCollectionId(null);
        assetRepo.save(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 -> {
            if (!p.getName().equals("铸造")) {
                p.setOpened(true);
                p.setOpenTime(LocalDateTime.now());
                p.setOpenedBy(SecurityUtils.getAuthenticatedUser().getId());
            }
        });
        assetRepo.save(asset);
    }

    public void transfer(Asset asset, BigDecimal price, User toUser, TransferReason reason, Long orderId) {
        Objects.requireNonNull(asset, "原藏品不能为空");
        Objects.requireNonNull(toUser, "转让人不能为空");
        Objects.requireNonNull(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());
        newAsset.setType(CollectionType.DEFAULT);
        newAsset.setSource(TransferReason.GIFT == reason ? AssetSource.GIFT : AssetSource.TRANSFER);
        newAsset.setTags(new HashSet<>(asset.getTags()));
        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.getDescription())
                .price(TransferReason.GIFT == reason ? null : price)
                .build());

        asset.setPublicShow(false);
        asset.setConsignment(false);
        asset.setPublicCollectionId(null);
        asset.setStatus(TransferReason.GIFT == reason ? AssetStatus.GIFTED : AssetStatus.TRANSFERRED);
        asset.setOwner(toUser.getNickname());
        asset.setOwnerId(toUser.getId());
        asset.setOwnerAvatar(toUser.getAvatar());
        assetRepo.save(asset);

        //vip权限转让
        CollectionPrivilege collectionPrivilege = collectionPrivilegeRepo.findByCollectionId(asset.getCollectionId());
        if (ObjectUtils.isNotEmpty(collectionPrivilege)) {
            if (collectionPrivilege.isVip()) {
                //更新vip信息
                userRepo.updateVipPurchase(toUser.getId(), 1);
                userRepo.updateVipPurchase(asset.getUserId(), 0);
            }
        }
    }

    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())
                        .build();
                t.setCreatedAt(asset.getCreatedAt());
                tokenHistoryRepo.save(t);
            } catch (Exception e) {
            }
        });
    }

    public Page<UserHistory> userHistory(Long userId, Long toUserId, Long fromUserId, Pageable pageable) {
        Page<TokenHistory> page;
        if (ObjectUtils.isNotEmpty(toUserId)) {
            page = tokenHistoryRepo.userHistoryTo(userId, toUserId, pageable);
        } else if (ObjectUtils.isNotEmpty(fromUserId)) {
            page = tokenHistoryRepo.userHistoryFrom(userId, fromUserId, pageable);
        } else {
            page = tokenHistoryRepo.userHistory(userId, pageable);
        }
        Set<String> tokenIds = page.stream().map(TokenHistory::getTokenId).collect(Collectors.toSet());
        List<Asset> assets = tokenIds.isEmpty() ? new ArrayList<>() : assetRepo.findByTokenIdIn(tokenIds);

        return page.map(tokenHistory -> {
            UserHistory userHistory = new UserHistory();
            BeanUtils.copyProperties(tokenHistory, userHistory);
            Optional<Asset> asset = assets.stream().filter(a -> a.getTokenId().equals(tokenHistory.getTokenId()))
                    .findAny();
            userHistory.setAssetName(asset.map(Asset::getName).orElse(null));
            userHistory.setPic(asset.map(Asset::getPic).orElse(new ArrayList<>()));
            switch (tokenHistory.getOperation()) {
                case "出售":
                case "转让":
                    userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "作品交易——买入" : "作品交易——售出");
                    break;
                case "空投":
                    userHistory.setDescription("空投");
                    break;
                case "转赠":
                    userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "他人赠送" : "作品赠送");
                    break;
            }
            return userHistory;
        });
    }

    public Page<UserHistory> userHistory(Long userId, PageQuery pageQuery) {
        Page<TokenHistory> page = tokenHistoryRepo.findAll(((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> and = JpaUtils
                    .toPredicates(pageQuery, TokenHistory.class, root, criteriaQuery, criteriaBuilder);
            Map<String, Object> query = pageQuery.getQuery();
            if (ObjectUtils.isEmpty(query.get("toUserId")) && ObjectUtils.isEmpty(query.get("fromUserId"))) {
                and.add(criteriaBuilder.or(criteriaBuilder.equal(root.get("toUserId"), userId), criteriaBuilder
                        .equal(root.get("fromUserId"), userId)));
            } else {
                if (ObjectUtils.isNotEmpty(query.get("toUserId"))) {
                    and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("toUserId"), userId)));
                } else {
                    and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("fromUserId"), userId)));
                }
            }
            return criteriaBuilder.and(and.toArray(new Predicate[0]));
        }), JpaUtils.toPageRequest(pageQuery));

        Set<String> tokenIds = page.stream().map(TokenHistory::getTokenId).collect(Collectors.toSet());
        List<Asset> assets = tokenIds.isEmpty() ? new ArrayList<>() : assetRepo.findByTokenIdIn(tokenIds);

        return page.map(tokenHistory -> {
            UserHistory userHistory = new UserHistory();
            BeanUtils.copyProperties(tokenHistory, userHistory);
            Optional<Asset> asset = assets.stream().filter(a -> a.getTokenId().equals(tokenHistory.getTokenId()))
                    .findAny();
            userHistory.setAssetName(asset.map(Asset::getName).orElse(null));
            userHistory.setPic(asset.map(Asset::getPic).orElse(new ArrayList<>()));
            switch (tokenHistory.getOperation()) {
                case "出售":
                case "转让":
                    userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "作品交易——买入" : "作品交易——售出");
                    break;
                case "空投":
                    userHistory.setDescription("空投");
                    break;
                case "转赠":
                    userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "他人赠送" : "作品赠送");
                    break;
            }
            return userHistory;
        });
    }

    public String mint(LocalDateTime time) {
        if (time == null) {
            time = LocalDateTime.now();
        }
        for (Asset asset : assetRepo.toMint(time)) {
            rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
        }
        return "ok";
    }

    @Cacheable(value = "userStat", key = "#userId")
    public Map<String, BigDecimal> breakdown(Long userId) {
        List<TokenHistory> page = tokenHistoryRepo.userHistory(userId);
        BigDecimal sale = page.stream()
                .filter(th -> th.getFromUserId().equals(userId) && ObjectUtils.isNotEmpty(th.getPrice()))
                .map(TokenHistory::getPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal buy = page.stream()
                .filter(th -> th.getToUserId().equals(userId) && ObjectUtils.isNotEmpty(th.getPrice()))
                .map(TokenHistory::getPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        Map<String, BigDecimal> map = new HashMap<>();
        map.put("sale", sale);
        map.put("buy", buy);
        return map;
    }

    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://raex-meta\\.oss-cn-shenzhen\\.aliyuncs\\.com",
                "https://cdn.raex.vip");
    }


//    @Scheduled(cron = "0 0 0/1 * * ?")
//    public void offTheShelf() {
//        LocalDateTime lastTime = LocalDateTime.now().minusHours(120);
//        Set<Long> assetIds = collectionRepo
//                .findResaleCollectionPriceOver20K(BigDecimal
//                        .valueOf(20000L), CollectionSource.TRANSFER, lastTime, true);
//        assetIds.forEach(this::cancelConsignmentBySystem);
//    }

    @Scheduled(cron = "0 0 0/1 * * ?")
    public void offTheShelfAll() {
        LocalDateTime lastTime = LocalDateTime.now().minusHours(240);
        Set<Long> assetIds = collectionRepo
                .findResaleCollectionPriceOver20K(BigDecimal
                        .valueOf(0L), CollectionSource.TRANSFER, lastTime, true);
        assetIds.forEach(this::cancelConsignmentBySystem);
    }


    public void cancelConsignmentBySystem(Long id) {
        Asset asset = assetRepo.findById(id).orElseThrow(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);
    }

    @Cacheable(cacheNames = "fmaa", key = "#userId+'#'+#mintActivityId+'#'+#pageable.hashCode()")
    public PageWrapper<Asset> findMintActivityAssetsWrap(Long userId, Long mintActivityId, Pageable pageable) {
        return PageWrapper.of(findMintActivityAssets(userId, mintActivityId, pageable));
    }

    public Page<Asset> findMintActivityAssets(Long userId, Long mintActivityId, Pageable pageable) {
        MintActivity mintActivity = mintActivityRepo.findById(mintActivityId).orElse(null);
        if (mintActivity == null) return new PageImpl<>(Collections.emptyList());

        if (!mintActivity.isAudit()) {
            Set<Tag> tags = mintActivity.getRule().getTags();
            if (tags.isEmpty()) return new PageImpl<>(Collections.emptyList());
            return assetRepo.findAll((Specification<Asset>) (root, query, criteriaBuilder) ->
                    query.distinct(true).where(criteriaBuilder.equal(root.get("userId"), userId),
                            criteriaBuilder.equal(root.get("status"), AssetStatus.NORMAL),
                            root.join("tags").get("id").in(tags.stream().map(Tag::getId).toArray()))
                            .getRestriction(), pageable);
        } else {
            return assetRepo.findByUserIdAndStatusAndNameLike(userId, AssetStatus.NORMAL,
                    "%" + mintActivity.getCollectionName() + "%", pageable);
        }
    }
}
