package com.izouma.nineth.service;

import com.google.common.collect.Lists;
import com.izouma.nineth.TokenHistory;
import com.izouma.nineth.config.Constants;
import com.izouma.nineth.config.GeneralProperties;
import com.izouma.nineth.converter.LongArrayConverter;
import com.izouma.nineth.domain.Collection;
import com.izouma.nineth.domain.*;
import com.izouma.nineth.dto.*;
import com.izouma.nineth.enums.*;
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.RandomStringUtils;
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.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.Predicate;
import java.math.BigDecimal;
import java.time.Duration;
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.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 ShowroomRepo            showroomRepo;
    private ShowCollectionRepo      showCollectionRepo;
    private CollectionPrivilegeRepo collectionPrivilegeRepo;
    private PasswordEncoder         passwordEncoder;
    private MintActivityRepo        mintActivityRepo;
    private DestroyRecordRepo       destroyRecordRepo;
    private AirDropService          airDropService;
    private HCChainService          hcChainService;
    private RockRecordService       rockRecordService;
    private RockRecordRepo          rockRecordRepo;
    private AssetLockRepo           assetLockRepo;
    private UserBalanceService      userBalanceService;

    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 List<AssetDTO> userSummary(PageQuery pageQuery) {
        List<AssetDTO> assetDTOs = new ArrayList<>();
        // 根据条件查询所有资产
        List<Asset> assets = assetRepo.findAll(JpaUtils.toSpecification(pageQuery, Asset.class));
        if (CollectionUtils.isEmpty(assets)) {
            return assetDTOs;
        }
        // 取出资产中未开启盲盒数据
        List<Asset> blindBoxClosedAssets = assets.stream()
                .filter(asset -> !asset.isOpened() && CollectionType.BLIND_BOX.equals(asset.getType()))
                .collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(blindBoxClosedAssets)) {
            blindBoxClosedAssets.forEach(asset -> {
                assetDTOs.add(AssetDTO.create(Lists.newArrayList(asset)));
            });
            // 移除资产中未开启盲盒数据
            assets.removeAll(blindBoxClosedAssets);
        }
        // 取出资产中所有未设置prefixName的值
        List<Asset> prefixNameIsNullAssets = assets.stream()
                .filter(asset -> StringUtils.isBlank(asset.getPrefixName()))
                .collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(prefixNameIsNullAssets)) {
            prefixNameIsNullAssets.forEach(asset -> {
                assetDTOs.add(AssetDTO.create(Lists.newArrayList(asset)));
            });
            assets.removeAll(prefixNameIsNullAssets);
        }
        if (CollectionUtils.isNotEmpty(assets)) {
            // 取出资产中所有prefixName
            List<String> prefixNames = assets.stream()
                    .map(Asset::getPrefixName)
                    .distinct()
                    .collect(Collectors.toList());
            // 将资产中相同prefixName归类（除未开启盲盒和未设置prefixName）
            prefixNames.forEach(str -> {
                List<Asset> collect = assets.stream()
                        .filter(asset -> str.equals(asset.getPrefixName()))
                        .collect(Collectors.toList());
                assetDTOs.add(AssetDTO.create(collect));
            });
        }
        return assetDTOs;
    }

    public Asset createAsset(Collection collection, User user, Long orderId, BigDecimal price, String type,
                             Integer number, boolean safeFlag) {
        Asset asset = Asset.create(collection, user);
        asset.setTokenId(TokenUtils.genTokenId());
        asset.setNumber(number);
        asset.setOasisId(collection.getOasisId());
        asset.setOrderId(orderId);
        asset.setPrice(price);
        asset.setPrefixName(collection.getPrefixName());
        asset.setTags(new HashSet<>());
        if (collection.getTags() != null) {
            asset.getTags().addAll(collection.getTags());
        }
        User fakeUser = null;
        if (safeFlag) {
            fakeUser = createFakeUser();
            asset.setOwner(fakeUser.getNickname());
            asset.setOwnerId(fakeUser.getId());
            asset.setOwnerAvatar(fakeUser.getAvatar());
        }
        assetRepo.saveAndFlush(asset);
        tokenHistoryRepo.save(TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(collection.getMinter())
                .fromUserId(collection.getMinterId())
                .fromAvatar(collection.getMinterAvatar())
                .toUser((safeFlag ? fakeUser : user).getNickname())
                .toUserId((safeFlag ? fakeUser : user).getId())
                .toAvatar((safeFlag ? fakeUser : user).getAvatar())
                .operation(type)
                .price(price)
                .build());

        //绿洲石
        rockRecordService.addRock(user.getId(), price, "购买");

        rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
        if (asset.getOasisId() != null & asset.getSource().equals(AssetSource.OFFICIAL)) {
            AirDrop airDrop = new AirDrop();
            airDrop.setName("建筑空投展厅");
            airDrop.setCollectionId(207012L);
            List<Long> userIds = new ArrayList<>();
            userIds.add(user.getId());
            List<Long> nums = new ArrayList<>();
            nums.add(1L);
            airDrop.setType(AirDropType.asset);
            List<DropTarget> dropTargets = new ArrayList<>();
            DropTarget dropTarget = new DropTarget();
            dropTarget.setNickname(user.getNickname());
            dropTarget.setNum(1);
            dropTarget.setPhone(user.getPhone());
            dropTarget.setUserId(user.getId());
            dropTargets.add(dropTarget);
            airDrop.setTargets(dropTargets);
            airDrop.setUserIds(userIds);
            airDrop.setNum(nums);
            airDropService.create(airDrop);

        }
        return asset;
    }

    public Asset createAsset(BlindBoxItem winItem, User user, Long orderId, BigDecimal price, String type,
                             Integer number, Integer holdDays, boolean safeFlag) {
        Collection blindBox = collectionRepo.findDetailById(winItem.getBlindBoxId())
                .orElseThrow(new BusinessException("盲盒不存在"));
        Collection collection = collectionRepo.findDetailById(winItem.getCollectionId())
                .orElseThrow(new BusinessException("藏品不存在"));
        winItem.setCollection(collection);
        Asset asset = Asset.create(winItem, user, holdDays);
        asset.setTokenId(TokenUtils.genTokenId());
        asset.setNumber(number);
        asset.setOasisId(winItem.getOasisId());
        asset.setOrderId(orderId);
        asset.setPrice(price);
        asset.setPrefixName(collection.getPrefixName());
        asset.setEmpower(collection.getEmpower());
        asset.setTags(new HashSet<>());
        if (blindBox.getTags() != null) {
            asset.getTags().addAll(blindBox.getTags());
        }
        if (collection.getTags() != null) {
            asset.getTags().addAll(collection.getTags());
        }
        User fakeUser = null;
        if (safeFlag) {
            fakeUser = createFakeUser();
            asset.setOwner(fakeUser.getNickname());
            asset.setOwnerId(fakeUser.getId());
            asset.setOwnerAvatar(fakeUser.getAvatar());
        }
        assetRepo.saveAndFlush(asset);
        tokenHistoryRepo.save(TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(winItem.getMinter())
                .fromUserId(winItem.getMinterId())
                .fromAvatar(winItem.getMinterAvatar())
                .toUser((safeFlag ? fakeUser : user).getNickname())
                .toUserId((safeFlag ? fakeUser : user).getId())
                .toAvatar((safeFlag ? fakeUser : user).getAvatar())
                .operation(type)
                .price(price)
                .build());

        //绿洲石
        rockRecordService.addRock(user.getId(), price, "购买");

        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.getLockTo() != null && asset.getLockTo().isAfter(LocalDateTime.now())) {
            throw new BusinessException("已锁仓，不能上架展示");
        }
        if (asset.isPublicShow()) {
            return;
        }
        if (asset.getStatus() != AssetStatus.NORMAL) {
            throw new BusinessException("当前状态不可展示");
        }
        User owner = asset.isSafeFlag() ?
                userRepo.findById(asset.getOwnerId()).orElseThrow(new BusinessException("用户不存在"))
                : 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())
                .oasisId(asset.getOasisId())
                .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())
                .tags(new HashSet<>())
                .prefixName(asset.getPrefixName())
                .companyId(asset.getCompanyId())
                .build();
        if (asset.getTags() != null) {
            collection.getTags().addAll(asset.getTags());
        }
        collectionRepo.save(collection);
        asset.setPublicShow(true);
        asset.setPublicCollectionId(collection.getId());
        assetRepo.saveAndFlush(asset);
    }

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

        if (asset.getLockTo() != null && asset.getLockTo().isAfter(LocalDateTime.now())) {
            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 (holdDays == 0 && AssetSource.OFFICIAL.equals(asset.getSource())) {
            BigDecimal officialConsignment = sysConfigService.getBigDecimal("OFFICIAL_CONSIGNMENT");
            //天转小时
            int hour = officialConsignment.multiply(new BigDecimal("24")).intValue();
            if (ChronoUnit.HOURS.between(asset.getCreatedAt(), LocalDateTime.now()) < hour) {
                throw new BusinessException("需持有满" + hour + "小时后才能寄售上架");
            }
        }

        if (ChronoUnit.DAYS.between(asset.getCreatedAt(), LocalDateTime.now()) < holdDays) {
            throw new BusinessException("需持有满" + holdDays + "天才能寄售上架");
        }
        User owner;
        if (safeFlag && !asset.isSafeFlag()) {
            owner = createFakeUser();
            asset.setOwner(owner.getNickname());
            asset.setOwnerId(owner.getId());
            asset.setOwnerAvatar(owner.getAvatar());
            asset.setSafeFlag(true);
            tokenHistoryRepo.findByTokenIdOrderByCreatedAtDesc(asset.getTokenId()).stream()
                    .filter(t -> t.getToUserId().equals(asset.getUserId())).findFirst()
                    .ifPresent(tokenHistory -> {
                        tokenHistory.setToUserId(owner.getId());
                        tokenHistory.setToUser(owner.getNickname());
                        tokenHistory.setToAvatar(owner.getAvatar());
                        tokenHistoryRepo.save(tokenHistory);
                    });
        } else {
            owner = asset.isSafeFlag() ?
                    userRepo.findById(asset.getOwnerId()).orElseThrow(new BusinessException("用户不存在"))
                    : userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
        }
        if (!passwordEncoder.matches(tradeCode, userRepo.findTradeCode(asset.getUserId()))) {
            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())
                .oasisId(asset.getOasisId())
                .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())
                .tags(new HashSet<>())
                .prefixName(asset.getPrefixName())
                .companyId(asset.getCompanyId())
                .build();
        if (asset.getTags() != null) {
            collection.getTags().addAll(asset.getTags());
        }
        collectionRepo.save(collection);
        asset.setPublicShow(true);
        asset.setConsignment(true);
        asset.setPublicCollectionId(collection.getId());
        asset.setSellPrice(price);
        assetRepo.saveAndFlush(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.saveAndFlush(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.saveAndFlush(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.saveAndFlush(asset);
    }

    public void transfer(Asset asset, BigDecimal price, User toUser, TransferReason reason, Long orderId) {
        transfer(asset, price, toUser, reason, orderId, false);
    }

    private User createFakeUser() {
        String name = "0x" + RandomStringUtils.randomAlphabetic(8);
        return userRepo.save(User.builder()
                .username(name)
                .nickname(name)
                .avatar(Constants.DEFAULT_AVATAR)
                .isPublicShow(true)
                .build());
    }

    public void transfer(Asset asset, BigDecimal price, User toUser, TransferReason reason, Long orderId, boolean safeFlag) {
        Objects.requireNonNull(asset, "原藏品不能为空");
        Objects.requireNonNull(toUser, "转让人不能为空");
        Objects.requireNonNull(reason, "转让原因不能为空");

        User newOwner = toUser;
        if (safeFlag) {
            newOwner = createFakeUser();
        }

        Asset newAsset = new Asset();
        BeanUtils.copyProperties(asset, newAsset);
        newAsset.setId(null);
        newAsset.setUserId(toUser.getId());
        newAsset.setOwner(newOwner.getNickname());
        newAsset.setOwnerId(newOwner.getId());
        newAsset.setOwnerAvatar(newOwner.getAvatar());
        newAsset.setPublicShow(false);
        newAsset.setConsignment(false);
        newAsset.setPublicCollectionId(null);
        newAsset.setStatus(AssetStatus.NORMAL);
        newAsset.setPrice(price);
        newAsset.setSellPrice(null);
        newAsset.setOrderId(orderId);
        newAsset.setOasisId(asset.getOasisId());
        newAsset.setFromAssetId(asset.getId());
        newAsset.setType(CollectionType.DEFAULT);
        newAsset.setSource(TransferReason.GIFT == reason ? AssetSource.GIFT : AssetSource.TRANSFER);
        newAsset.setTags(new HashSet<>(asset.getTags()));
        newAsset.setSafeFlag(safeFlag);
        newAsset.setHoldDays(asset.getOldHoldDays());
        assetRepo.saveAndFlush(newAsset);

        TokenHistory tokenHistory = TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(asset.getOwner())
                .fromUserId(asset.getOwnerId())
                .fromAvatar(asset.getOwnerAvatar())
                .toUser(newOwner.getNickname())
                .toUserId(newOwner.getId())
                .toAvatar(newOwner.getAvatar())
                .operation(reason.getDescription())
                .price(TransferReason.GIFT == reason ? null : price)
                .build();
        tokenHistoryRepo.save(tokenHistory);

        //购买者加绿洲石
        if (TransferReason.TRANSFER.equals(reason) || TransferReason.AUCTION.equals(reason)) {
            rockRecordService.addRock(newOwner.getId(), price, "购买");
        }

        asset.setPublicShow(false);
        asset.setConsignment(false);
        asset.setPublicCollectionId(null);
        switch (reason) {
            case GIFT:
                asset.setStatus(AssetStatus.GIFTED);
                break;
            case AUCTION:
                asset.setStatus(AssetStatus.AUCTIONED);
                break;
            case TRANSFER:
                asset.setStatus(AssetStatus.TRANSFERRED);
        }
        asset.setOwner(newOwner.getNickname());
        asset.setOwnerId(newOwner.getId());
        asset.setOwnerAvatar(newOwner.getAvatar());
        assetRepo.saveAndFlush(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(tokenHistory.getToUserId().equals(userId) ? "他人赠送" : "作品赠送");
                    break;
                default:
                    userHistory.setDescription(tokenHistory.getOperation());
            }
            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(tokenHistory.getToUserId().equals(userId) ? "他人赠送" : "作品赠送");
                    break;
                default:
                    userHistory.setDescription(tokenHistory.getOperation());
            }
            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", tokenHistoryRepo.userSale(userId));
        map.put("buy", rockRecordService.getRock(userId).getRecord());
        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.saveAndFlush(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(
                                    // where userId=some id
                                    criteriaBuilder.equal(root.get("userId"), userId),
                                    // and (lockTo is null or (lockTo is not null and lockTo < now))
                                    criteriaBuilder.or(criteriaBuilder.isNull(root.get("lockTo")),
                                            criteriaBuilder.and(criteriaBuilder.isNotNull(root.get("lockTo")),
                                                    criteriaBuilder.lessThan(root.get("lockTo"), LocalDateTime.now()))),
                                    // and status = 'NORMAL'
                                    criteriaBuilder.equal(root.get("status"), AssetStatus.NORMAL),
                                    // and has some tagId
                                    root.join("tags").get("id").in(tags.stream().map(Tag::getId).toArray()))
                            .getRestriction(), pageable);
        } else {
            return assetRepo.findByUserIdAndStatusAndNameLike(userId, AssetStatus.NORMAL,
                    "%" + mintActivity.getCollectionName() + "%", pageable);
        }
    }

    public void destroy(Long id, Long userId ,String tradeCode) {
        Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        if (!asset.getUserId().equals(userId)) {
            throw new BusinessException("此藏品不属于你");
        }
        if (asset.getStatus() != AssetStatus.NORMAL) {
            throw new BusinessException("当前状态不可销毁");
        }
        if (asset.isPublicShow()) {
            throw new BusinessException("请先取消公开展示");
//            cancelPublic(asset);
        }
        User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
        if (StringUtils.isEmpty(user.getTradeCode())){
            throw new BusinessException("未设置交易密码");
        }
        if (!passwordEncoder.matches(tradeCode, user.getTradeCode())) {
            throw new BusinessException("交易密码错误");
        }

        User toUser = userRepo.findById(Constants.BLACK_HOLE_USER_ID).orElseThrow(new BusinessException("无记录"));

        TokenHistory tokenHistory = TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(asset.getOwner())
                .fromUserId(asset.getOwnerId())
                .fromAvatar(asset.getOwnerAvatar())
                .toUser(toUser.getNickname())
                .toUserId(toUser.getId())
                .toAvatar(toUser.getAvatar())
                .operation(TransferReason.DESTROY.getDescription())
                .price(null)
                .build();
        tokenHistoryRepo.save(tokenHistory);

        asset.setPublicShow(false);
        asset.setConsignment(false);
        asset.setPublicCollectionId(null);
        asset.setStatus(AssetStatus.DESTROYED);
        asset.setOwner(toUser.getNickname());
        asset.setOwnerId(toUser.getId());
        asset.setOwnerAvatar(toUser.getAvatar());
        assetRepo.saveAndFlush(asset);
        //积分记录
        destroyRecordRepo.save(DestroyRecord.builder()
                .userId(userId)
                .assetId(asset.getId())
                .name(asset.getName())
                .pic(asset.getPic().get(0).getUrl())
                .record(1)
                .type(RecordType.OBTAIN)
                .build());

        //加积分
        userRepo.addDestroyPoint(userId, 1);
    }
    public double getRoyalties(Long minterId, double royalties, Long userId) {
        if (royalties == 2) {
            return 2;
        }
        LongArrayConverter converter = new LongArrayConverter();
        String discountMinter = sysConfigService.getString("discount_minter");
        List<Long> minterIds = converter.convertToEntityAttribute(discountMinter);
        if (minterIds.contains(minterId)) {
            String discountCollection = sysConfigService.getString("discount_collection");
            List<Long> collectionIds = converter.convertToEntityAttribute(discountCollection);
            Long assetId = assetRepo.findDiscount(userId, collectionIds);
            if (ObjectUtils.isNotEmpty(assetId)) {
                return 2;
            }
        }
        return royalties;
    }

    @Async
    public void hcChain() throws ExecutionException, InterruptedException {
        new ForkJoinPool(1000).submit(() -> {
            AtomicInteger num = new AtomicInteger();
            assetRepo.findByStatus(AssetStatus.NORMAL).parallelStream()
                    .forEach(asset -> {
                        if (asset.getHcTxHash() == null) {
                            User user = userRepo.findById(asset.getUserId()).orElse(null);
                            if (user != null) {
                                if (user.getHcChainAddress() == null) {
                                    user.setHcChainAddress(hcChainService.createAccount(asset.getUserId()));
                                }
                                NFT nft = hcChainService.mint(user.getHcChainAddress(), asset.getTokenId());
                                asset.setHcTokenId(nft.getTokenId());
                                asset.setHcTxHash(nft.getTxHash());
                                asset.setGasUsed(nft.getGasUsed());
                                assetRepo.saveAndFlush(asset);
                            }
                        }
                        log.info("hcChain:" + num.getAndIncrement());
                    });
        }).get();
    }

    public void lockAsset(Long userId, Long assetId, Duration duration) {
        User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
        Asset asset = assetRepo.findById(assetId).orElseThrow(new BusinessException("藏品不存在"));
        if (!asset.getUserId().equals(userId)) {
            throw new BusinessException("无权限");
        }
        if (asset.getLockTo() != null && asset.getLockTo().isAfter(LocalDateTime.now())) {
            throw new BusinessException("已是锁仓状态");
        }
        if (asset.getType() == CollectionType.SHOWROOM) {
            throw new BusinessException("展厅不可锁定");
        }
        if (asset.getStatus() != AssetStatus.NORMAL) {
            throw new BusinessException("当前状态不可锁定");
        }
        if (asset.isPublicShow() || asset.isConsignment()) {
            throw new BusinessException("请先取消展示和寄售");
        }
        if (duration.compareTo(Duration.parse("P1D")) < 0) {
            throw new BusinessException("最小锁定1天");
        }
        asset.setLockAt(LocalDateTime.now());
        asset.setLockTo(asset.getLockAt().plus(duration));
        assetRepo.saveAndFlush(asset);

        assetLockRepo.save(AssetLock.builder()
                .userId(userId)
                .phone(user.getPhone())
                .nickname(user.getNickname())
                .assetId(assetId)
                .name(asset.getName())
                .number(asset.getNumber())
                .lockAt(asset.getLockAt())
                .lockTo(asset.getLockTo())
                .duration(duration)
                .build());
    }

    public List<MetaPlayerRoleClassify> metaPlayerRole(Long userId) {
        List<MetaPlayerRoleClassify> metaPlayerRoleClassifies = new ArrayList<>();
        metaPlayerRoleClassifies.add(buildMetaPlayerRoleClassify(userId, "艾弗森", 1L));
        metaPlayerRoleClassifies.add(buildMetaPlayerRoleClassify(userId, "开拓猿", 2L));
        metaPlayerRoleClassifies.add(buildMetaPlayerRoleClassify(userId, "朋克", 3L));
        metaPlayerRoleClassifies.add(buildMetaPlayerRoleClassify(userId, "MUGEN", 4L));
        return metaPlayerRoleClassifies;

    }

    private MetaPlayerRoleClassify buildMetaPlayerRoleClassify(Long userId, String name, Long id) {
        MetaPlayerRoleClassify metaPlayerRoleClassify = new MetaPlayerRoleClassify();
        metaPlayerRoleClassify.setClassify(name);
        List<MetaPlayerRole> metaPlayerRoleList;
        if("MUGEN".equals(name)) {
            metaPlayerRoleList = Arrays.asList(new MetaPlayerRole(id, "无限集团"),
                    new MetaPlayerRole(id + 1, "苍茫雷道"),
                    new MetaPlayerRole(id + 2, "炼炉天族"),
                    new MetaPlayerRole(id + 3, "未央宗"),
                    new MetaPlayerRole(id + 4, "八级门"),
                    new MetaPlayerRole(id + 5, "人族"),
                    new MetaPlayerRole(id + 6, "凤鸣寺"),
                    new MetaPlayerRole(id + 7, "九州岛"));
        } else {
            metaPlayerRoleList = Collections.singletonList(new MetaPlayerRole(id, name));
        }
        metaPlayerRoleClassify.setMetaPlayerRoleList(build(userId, metaPlayerRoleList));
        return metaPlayerRoleClassify;
    }

    private List<MetaPlayerRole> build (Long userId, List<MetaPlayerRole> metaPlayerRoleList) {
        metaPlayerRoleList.forEach(metaPlayerRole -> {
            metaPlayerRole.setType(UserHoldTypeEnum.ASSET);
            metaPlayerRole.setAddress("https://www.raex.vip/9th/productSearch?search=" + metaPlayerRole.getName() + "&source=TRANSFER");
            List<Asset> assets = assetRepo.findAllByUserIdAndNameLike(userId, "%" + metaPlayerRole.getName() + "%");
            metaPlayerRole.setHold(CollectionUtils.isNotEmpty(assets));
        });
        return metaPlayerRoleList;
    }
}
