package com.izouma.nineth.service;

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.dto.CollectionDTO;
import com.izouma.nineth.dto.CreateBlindBox;
import com.izouma.nineth.dto.PageQuery;
import com.izouma.nineth.dto.PageWrapDTO;
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.collection.CollectionCategoryService;
import com.izouma.nineth.utils.JpaUtils;
import com.izouma.nineth.utils.SecurityUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.Range;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.query.criteria.internal.OrderImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.*;
import javax.transaction.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@AllArgsConstructor
public class CollectionService {
    private CollectionRepo collectionRepo;
    private LikeRepo likeRepo;
    private BlindBoxItemRepo blindBoxItemRepo;
    private AppointmentRepo appointmentRepo;
    private UserRepo userRepo;
    private TaskScheduler taskScheduler;
    private AssetRepo assetRepo;
    private RedisTemplate<String, Object> redisTemplate;
    private UserWareHouseRepo userWareHouseRepo;
    private SysConfigService sysConfigService;
    private final Map<Long, ScheduledFuture<?>> tasks = new HashMap<>();
    private ActivityPriorityDocumentRepo activityPriorityDocumentRepo;
    private final CollectionCategoryService collectionCategoryService;
    private ActivityPriorityDocumentService activityPriorityDocumentService;
    private EntityManager entityManager;

    @PostConstruct
    public void init() {
        List<Collection> collections = collectionRepo.findByScheduleSaleTrueAndOnShelfFalseAndStartTimeBeforeAndDelFalse(LocalDateTime.now());
        for (Collection collection : collections) {
            onShelfTask(collection);
        }
    }

    /**
     * 查询藏品
     * @param pageQuery
     * @return
     */
    public Page<Collection> all(PageQuery pageQuery) {
        pageQuery.getQuery().put("del", false);
        String collectionCategoryId = (String) pageQuery.getQuery().get("collectionCategoryId");
        String type = MapUtils.getString(pageQuery.getQuery(), "type", "DEFAULT");
        pageQuery.getQuery().remove("type");
        pageQuery.getQuery().remove("collectionCategoryId");

        Specification<Collection> specification = JpaUtils.toSpecification(pageQuery, Collection.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()));
        }

        specification = specification.and((Specification<Collection>) (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> and = new ArrayList<>();

            if (StringUtils.isNotEmpty(type) && !"all".equalsIgnoreCase(type)) {
                try {
                    if (type.contains(",")) {
                        and.add(root.get("type")
                                .in(Arrays.stream(type.split(",")).map(s -> Enum.valueOf(CollectionType.class, s))
                                        .collect(Collectors.toList())));
                    } else {
                        and.add(criteriaBuilder.equal(root.get("type"), Enum.valueOf(CollectionType.class, type)));
                    }
                } catch (Exception ignored) {

                }
            }
            if (StringUtils.isNotBlank(collectionCategoryId)) {
                and.add(criteriaBuilder.gt(criteriaBuilder.function("find_in_set", Integer.class, criteriaBuilder.literal(collectionCategoryId), root.get("categories")),0));
            }
            return criteriaBuilder.and(and.toArray(new Predicate[0]));
        });
        return collectionRepo.findAll(specification, pageRequest);
    }

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

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

    /**
     * 二手藏品查询
     * @param pageQuery
     * @return
     */
    public PageWrapDTO<CollectionDTO> allSecondCollection(PageQuery pageQuery){
        pageQuery.getQuery().put("del", false);
        pageQuery.getQuery().put("source", "TRANSFER");//二手的

        String userId = Objects.toString(pageQuery.getQuery().remove("userId"), null);
        String phone = Objects.toString(pageQuery.getQuery().remove("phone"), null);
        String officialCollectionId = Objects.toString(pageQuery.getQuery().remove("officialCollectionId"), null);
        String assetStatusStr = Objects.toString(pageQuery.getQuery().remove("assetStatus"), null);

        //通用条件
        Specification<Collection> specification = JpaUtils.toSpecification(pageQuery, Collection.class);
        specification = specification.and((collectionRoot, criteriaQuery, builder) -> {
            Predicate predicate = builder.conjunction();

            Root<Asset> assetRoot = criteriaQuery.from(Asset.class);

            Root<User> userRoot = criteriaQuery.from(User.class);

            criteriaQuery.multiselect(collectionRoot, assetRoot, userRoot);

            //关联其他表
            predicate.getExpressions().add(builder.equal(assetRoot.get("id"), collectionRoot.get("assetId")));

            predicate.getExpressions().add(builder.equal(userRoot.get("id"), assetRoot.get("userId")));

            //官方藏品ID
            if(StringUtils.isNotEmpty(officialCollectionId)){
                predicate.getExpressions().add(builder.equal(assetRoot.get("collectionId"), officialCollectionId));
            }

            //资产状态
            AssetStatus assetStatus = null;
            if(StringUtils.isNotEmpty(assetStatusStr)){
                assetStatus = AssetStatus.valueOf(assetStatusStr);
            }
            if(assetStatus != null){
                predicate.getExpressions().add(builder.equal(assetRoot.get("status"), assetStatus));
            }


            //用户ID
            if(StringUtils.isNotEmpty(userId)){
                predicate.getExpressions().add(builder.equal(userRoot.get("id"), userId));
            }

            //用户手机号
            if(StringUtils.isNotEmpty(phone)){
                Expression<String> likeExp =  builder.function("concat", String.class,
                        builder.literal("%"), builder.literal(phone), builder.literal("%"));
                predicate.getExpressions().add(builder.like(userRoot.get("phone"), likeExp));
            }

            //只查看寄售的数据
            predicate.getExpressions().add(builder.equal(assetRoot.get("consignment"), true));

            return predicate;
        });

        PageRequest pageRequest = JpaUtils.toPageRequest(pageQuery);

        List<CollectionDTO> list = null;
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        {
            CriteriaQuery<CollectionDTO> criteriaQuery = builder.createQuery(CollectionDTO.class);

            Root<Collection> collectionRoot = criteriaQuery.from(Collection.class);

            Predicate predicate = specification.toPredicate(collectionRoot, criteriaQuery, builder);

            criteriaQuery.where(predicate);

            //排序
            List<Order> orders = new ArrayList<>();
            pageRequest.getSort().forEach(item -> {
                orders.add(new OrderImpl(collectionRoot.get(item.getProperty()), item.isAscending()));
            });
            criteriaQuery.orderBy(orders);


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

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

            list = typedQuery.getResultList();
        }

        Long total = 0L;

        {
            CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
            Root<Collection> collectionRoot = criteriaQuery.from(Collection.class);

            Predicate predicate = specification.toPredicate(collectionRoot, criteriaQuery, builder);

            criteriaQuery.select(builder.count(collectionRoot));

            criteriaQuery.where(predicate);

            TypedQuery<Long>  typedQuery = entityManager.createQuery(criteriaQuery);
            total = typedQuery.getSingleResult();
        }

        PageWrapDTO<CollectionDTO> pageWrapDTO = new PageWrapDTO<>(list, pageRequest, total);

        BigDecimal totalPrice = sumPrice(specification);
        pageWrapDTO.setTotalAmount(totalPrice);

        return pageWrapDTO;

    }


    /**
     * 创建普通藏品
     *
     * @param record
     * @return
     */
    @Transactional
    public Collection create(Collection record) {
        if (StringUtils.isNotBlank(record.getPicPattern())) {
            if (record.getPicPattern().indexOf("{}") == -1) {
                throw new BusinessException("主图模式信息需要包含占位符号{}");
            }

            record.setPicPattern(record.getPicPattern().trim());
        }
        if (record.isVariableType()) {
            if (record.getType() != CollectionType.BLIND_BOX) {
                throw new BusinessException("该设置必须选择盲盒");
            }
        }

        User minter = userRepo.findById(record.getMinterId()).orElse(SecurityUtils.getAuthenticatedUser());
        record.setMinter(minter.getNickname());
        record.setMinterId(minter.getId());
        record.setMinterAvatar(minter.getAvatar());
        record.setOwner(minter.getNickname());
        record.setOwnerId(minter.getId());
        record.setOwnerAvatar(minter.getAvatar());
        record.setStock(record.getTotal());
        record.setSale(0);
        if (record.isScheduleSale()) {
            if (record.getStartTime() == null) {
                throw new BusinessException("请填写定时发布时间");
            }
            if (record.getStartTime().isBefore(LocalDateTime.now())) {
                record.setOnShelf(true);
                record.setSalable(true);
                record.setStartTime(null);
                record.setScheduleSale(false);
            }
        }
        StringBuilder sb = new StringBuilder();
        if (record.getCollectionCategoryId() != null) {
            Long categoryId = record.getCollectionCategoryId();
            CollectionCategory category = collectionCategoryService.getById(categoryId);
            getCategories(sb, category);
            sb.deleteCharAt(sb.length() - 1);
            record.setCategories(sb.toString());
            CollectionCategory collectionCategory = new CollectionCategory();
            collectionCategory.setId(categoryId);
            record.setCollectionCategory(collectionCategory);
        }
        record = collectionRepo.save(record);
        onShelfTask(record);
        return record;
    }


    private void getCategories(StringBuilder sb,CollectionCategory category){
        sb.append(category.getId()).append(",");
        if (0L != category.getParentId()) {
            CollectionCategory parent = collectionCategoryService.getByParentId(category.getParentId());
            getCategories(sb,parent);
        }
    }

    @Transactional
    public Collection update(Collection record) {
        Collection oldCollection = collectionRepo.findById(record.getId()).orElseThrow(new BusinessException("记录未找到"));
        oldCollection.setOnShelf(record.isOnShelf());
        oldCollection.setSalable(record.isSalable());
        oldCollection.setStartTime(record.getStartTime());
        oldCollection.setScheduleSale(record.isScheduleSale());
        oldCollection.setSort(record.getSort());
        oldCollection.setDetail(record.getDetail());
        oldCollection.setPrivileges(record.getPrivileges());
        oldCollection.setProperties(record.getProperties());
        oldCollection.setModel3d(record.getModel3d());
        oldCollection.setLimitBuyCount(record.getLimitBuyCount());
        oldCollection.setVariableType(record.isVariableType());
        oldCollection.setCollectionCategory(record.getCollectionCategory());
        StringBuilder sb = new StringBuilder();
        if (record.getCollectionCategoryId() != null) {
            Long categoryId = record.getCollectionCategoryId();
            CollectionCategory category = collectionCategoryService.getById(categoryId);
            getCategories(sb, category);
            sb.deleteCharAt(sb.length() - 1);
            oldCollection.setCategories(sb.toString());
            assetRepo.updateCategoriesByCollectionId(sb.toString(), record.getId());
            collectionRepo.updateTransferCollection(categoryId, sb.toString(), record.getId());

            CollectionCategory collectionCategory = new CollectionCategory();
            collectionCategory.setId(categoryId);
            oldCollection.setCollectionCategory(collectionCategory);
        }
        collectionRepo.save(oldCollection);
        onShelfTask(oldCollection);
        return oldCollection;
    }

    /**
     * 初始化定时上架
     *
     * @param record
     */
    private void onShelfTask(Collection record) {
        ScheduledFuture<?> task = tasks.get(record.getId());
        if (task != null) {
            if (!task.cancel(true)) {
                return;
            }
        }
        if (record.isScheduleSale()) {
            if (record.getStartTime()!=null && record.getStartTime().minusSeconds(2).isAfter(LocalDateTime.now())) {
                Date date = Date.from(record.getStartTime().atZone(ZoneId.systemDefault()).toInstant());
                ScheduledFuture<?> future = taskScheduler.schedule(() -> {
                    collectionRepo.scheduleOnShelf(record.getId());
                    tasks.remove(record.getId());
                    try {
                        //官方开始发售，查询出需要重置的优先购重置，条件 1全局优先购需要重置，2可以购买改藏品的需要重置 3判断铸造者是否需要重置
                        activityPriorityDocumentRepo.findReset().forEach(document->{
                            if((ItemType.PERPETUAL_CERTIFICATE == document.getItemName() &&record.getMinterId().equals(document.getMinterId()))
                                    || ItemType.CONSUMABLE_CERTIFICATE == document.getItemName()
                                    ||(record.getId().equals(document.getTargetCollectionId()) ||Objects.requireNonNullElse(document.getTargetCollectionIdList(),new ArrayList<>()).contains(record.getId()))){

                                List<Asset> assets = assetRepo.findAssetHeldId(document.getCollectionId(), document.getProjectId());
                                activityPriorityDocumentService.resetAllUserWareHouseLsit(assets,document,true);
                            }
                        });
                    }catch (Exception e){
                        log.error("优先购重置异常",e);
                    }
                }, date);
                tasks.put(record.getId(), future);
            } else {
                collectionRepo.scheduleOnShelf(record.getId());
            }
        }
    }

    public CollectionDTO toDTO(Collection collection) {
        return toDTO(collection, true);
    }

    public CollectionDTO toDTO(Collection collection, boolean join) {
        CollectionDTO collectionDTO = new CollectionDTO();
        BeanUtils.copyProperties(collection, collectionDTO);
        if (join) {
            if (SecurityUtils.getAuthenticatedUser() != null) {
                List<Like> list = likeRepo.findByUserIdAndCollectionId(SecurityUtils.getAuthenticatedUser().getId(),
                        collection.getId());
                collectionDTO.setLiked(!list.isEmpty());
                if (collection.getType() == CollectionType.BLIND_BOX) {
                    collectionDTO.setAppointment(appointmentRepo.findFirstByBlindBoxId(collection.getId()).isPresent());
                }
            }
        }
        return collectionDTO;
    }

    public List<CollectionDTO> toDTO(List<Collection> collections) {
        List<Like> likes = new ArrayList<>();
        List<Appointment> appointments = new ArrayList<>();
        if (SecurityUtils.getAuthenticatedUser() != null) {
            likes.addAll(likeRepo.findByUserId(SecurityUtils.getAuthenticatedUser().getId()));
            appointments.addAll(appointmentRepo.findByUserId(SecurityUtils.getAuthenticatedUser().getId()));
        }
        return collections.stream().parallel().map(collection -> {
            CollectionDTO dto = toDTO(collection, false);
            if (!likes.isEmpty()) {
                dto.setLiked(likes.stream().anyMatch(l -> l.getCollectionId().equals(collection.getId())));
            }
            if (!appointments.isEmpty()) {
                dto.setAppointment(appointments.stream().anyMatch(a -> a.getBlindBoxId().equals(collection.getId())));
            }
            return dto;
        }).collect(Collectors.toList());
    }

    public Page<CollectionDTO> toDTO(Page<Collection> collections) {
        List<CollectionDTO> userDTOS = toDTO(collections.getContent());
        return new PageImpl<>(userDTOS, collections.getPageable(), collections.getTotalElements());
    }

    /**
     * 根据用户过滤白名单藏品
     *
     * @param all
     * @param user
     */
    public boolean filter(List<Collection> all, User user) {
        boolean flag = true;
        //需要屏蔽的藏品
        String[] collectionidArr = sysConfigService.getString(SysConstants.TEST_NAMED_COLLECTIONID).split(",");
        if (collectionidArr.length > 0) {
            List<String> list1 = Arrays.stream(collectionidArr).collect(Collectors.toList());

            List<Collection> onShelfList = new ArrayList<>();
            List<Collection> salableList = new ArrayList<>();
            for (Collection c : all) {
                Long id = c.getId();
                if (c.getAssetId() != null) {
                    Optional<Asset> ac = assetRepo.findById(c.getAssetId());
                    if (ac.isPresent()) {
                        id = ac.get().getCollectionId();
                    }
                }
                Long finalId = id;
                if (!list1.isEmpty() && list1.stream().anyMatch(n -> n.equals(String.valueOf(finalId)))) {
                    if (c.isOnShelf()) {
                        c.setOnShelf(false);
                        onShelfList.add(c);
                        flag = false;
                    }
                    if (c.isSalable()) {
                        c.setSalable(false);
                        salableList.add(c);
                        flag = false;
                    }
                }
            }

            //不需要屏蔽的玩家
            String[] named_userArr = sysConfigService.getString(SysConstants.TEST_NAMED_USER).split(",");
            if (user != null && named_userArr.length > 0) {
                List<String> list2 = Arrays.stream(named_userArr).collect(Collectors.toList());
                if (list2.stream().anyMatch(n -> n.equals(String.valueOf(user.getId())))) {
                    onShelfList.forEach(e -> e.setOnShelf(true));
                    salableList.forEach(e -> e.setSalable(true));
                    flag = true;
                }
            }
        }
        return flag;
    }


    /**
     * 筛选优先购道具
     *
     * @param userId       用户ID
     * @return
     */
    public List<UserWareHouse> getAdvancePurchaseTime(Long userId,Collection collection) {
        if (collection.getStartTime() == null) {
            return new ArrayList<>();
        }
        List<UserWareHouse> list = userWareHouseRepo.findUserIdItem(userId, collection.getProjectId());
        //筛选出优先购凭证
        list = list.stream().filter(e -> ItemType.PERPETUAL_CERTIFICATE.equals(e.getItemName())
                || ItemType.CONSUMABLE_CERTIFICATE.equals(e.getItemName())
                || ItemType.CONSUMABLE_ASSIGN_CERTIFICATE.equals(e.getItemName())).collect(Collectors.toList());
        if (list.isEmpty()){
            return new ArrayList<>();
        }

        //过滤掉时间没到的，或者资产状态不正常的优先购
        list.removeIf(e -> {
            //过滤过期了
            LocalDateTime overdueTime  = e.getOverdueTime();
            if (overdueTime!=null && LocalDateTime.now().isAfter(overdueTime)){
                userWareHouseRepo.softDelete(e.getId());
                return true;
            }
            //过滤时间没到的
            if (collection.getStartTime().isAfter(LocalDateTime.now().plusHours(e.getAdvancePurchaseTime())) || e.getAdvancePurchaseCount() <= 0){
                return true;
            }
            //如果是全局的 不用判断后面了 直接过
            if (ItemType.CONSUMABLE_CERTIFICATE.equals(e.getItemName())){
                return false;
            }
            //如果是指定铸造者,判断改藏品是不是指定铸造者  如果不是就过滤掉
            if (ItemType.PERPETUAL_CERTIFICATE.equals(e.getItemName())){
                return !collection.getMinterId().equals(e.getMinterId());
            }

            //并且不是白名单，资产不正常不能使用删除
            if (e.getAssetId()!=null){
                Optional<Asset> asset = assetRepo.findById(e.getAssetId());
                if (!asset.isPresent() || !asset.get().getStatus().equals(AssetStatus.NORMAL)){
                    return true;
                }
            }
            //如果是指定藏品,不包含该藏品的也删除
            return !(Objects.equals(e.getTargetCollectionId(), collection.getId()) || (e.getTargetCollectionIdList() != null && e.getTargetCollectionIdList().stream().anyMatch(t -> Objects.equals(t, collection.getId()))));
        });
        if (!list.isEmpty()){
            //白名单优先购

            //不是白名单
            return getResult(list);
        }
        return new ArrayList<>();
    }

    /**
     * 白名单 与 藏品优先购下面都有：可重置的优先购 ，铸造者优先购 ， 消耗性指定藏品优先购凭证 ， 消耗性全局优先购凭证
     * 白名单 与 藏品优先购，优先使用白名单的。
     * 优先购使用优先级 ：白名单 -> 可重置的优先购 -> 铸造者优先购 -> 消耗性指定藏品优先购凭证 -> 消耗性全局优先购凭证
     * @param wareHouseList
     * @return
     */
    private List<UserWareHouse> getResult( List<UserWareHouse> wareHouseList){
        List<UserWareHouse> preBuyList = new ArrayList<>();
        //白名单
        List<UserWareHouse> whiteList = wareHouseList.stream().filter(ac -> ac.getAssetId()==null).collect(Collectors.toList());
        if (!whiteList.isEmpty()) {
            preBuyList.addAll(sortUserWareHouse(whiteList));
        }

        //可重置的优先购
        List<Long> list = activityPriorityDocumentRepo.findReset().stream().map(ActivityPriorityDocument::getId).collect(Collectors.toList());
        if (!list.isEmpty()){
            List<UserWareHouse> result = wareHouseList.stream().filter(item ->list.contains(item.getActivityPriorityDocumentId()) ).collect(Collectors.toList());
            if (!result.isEmpty()) {
                preBuyList.addAll(sortUserWareHouse(result));
            }
        }
        //铸造者优先购
        List<UserWareHouse> result = wareHouseList.stream().filter(item ->ItemType.PERPETUAL_CERTIFICATE.equals(item.getItemName()) ).collect(Collectors.toList());
        if (!result.isEmpty()) {
            preBuyList.addAll(sortUserWareHouse(result));
        }
        //消耗性指定藏品优先购凭证
        result = wareHouseList.stream().filter(item ->ItemType.CONSUMABLE_ASSIGN_CERTIFICATE.equals(item.getItemName())).collect(Collectors.toList());
        if (!result.isEmpty()) {
            preBuyList.addAll(sortUserWareHouse(result));
        }
        //消耗性全局优先购凭证
        result = wareHouseList.stream().filter(item ->ItemType.CONSUMABLE_CERTIFICATE.equals(item.getItemName())).collect(Collectors.toList());
        if (!result.isEmpty()) {
            preBuyList.addAll(sortUserWareHouse(result));
        }
        return preBuyList;
    }

    /**
     * 显示优先购次数 类型
     * @param collections
     */
    public void displayPriorityOrder(List<CollectionDTO> collections){
        //是寄售的查询一下有没有优先购
        for(CollectionDTO collection :collections){
            if (collection.getAssetId()!=null && collection.getAssetId()>0){
                List<UserWareHouse> list = userWareHouseRepo.findByAssetId(collection.getAssetId());
                if (!list.isEmpty()){
                    UserWareHouse ac = list.get(0);
                    collection.setPreEmptionPrivilege(new PreEmptionPrivilege(ac.getAdvancePurchaseCount(),ac.getItemId()));
                }
            }
        }
    }

    /**
     * 优先购凭证按照时间排序， 如果最小时间的有多个凭证的情况按照次数排序，如果还是有多个凭证随便拿永久性的，否则随便拿一个
     *
     * @param list
     * @return
     */
    private List<UserWareHouse> sortUserWareHouse(List<UserWareHouse> list) {
        if (list == null || list.isEmpty()){
            return new ArrayList<>();
        }
        //按照时间排序  用时间最近的 时间相同 先排次数少的
        return list.stream()
                .sorted(Comparator.comparingInt(UserWareHouse::getAdvancePurchaseTime)
                        .thenComparingInt(UserWareHouse::getAdvancePurchaseCount))
                .collect(Collectors.toList());
    }

    @Transactional
    public Collection createBlindBox(CreateBlindBox createBlindBox) {
        Collection blindBox = createBlindBox.getBlindBox();
        if (blindBox.getId() != null) {
            throw new BusinessException("无法完成此操作");
        }
        if (StringUtils.isNotEmpty(blindBox.getPicPattern())) {
            throw new BusinessException("盲盒不支持主图模式");
        }

        List<Collection> list =
                collectionRepo.findAllById(createBlindBox.getItems().stream().map(BlindBoxItem::getCollectionId)
                        .collect(Collectors.toSet()));
        for (BlindBoxItem item : createBlindBox.getItems()) {
            Collection collection = list.stream().filter(i -> i.getId().equals(item.getCollectionId())).findAny()
                    .orElseThrow(new BusinessException("所选藏品不存在"));
            if (item.getTotal() > collection.getStock()) {
                throw new BusinessException("所选藏品库存不足:" + collection.getName());
            }
        }

        User user = userRepo.findById(blindBox.getMinterId()).orElse(SecurityUtils.getAuthenticatedUser());
        blindBox.setMinter(user.getNickname());
        blindBox.setMinterId(user.getId());
        blindBox.setMinterAvatar(user.getAvatar());
        blindBox.setOwner(user.getNickname());
        blindBox.setOwnerId(user.getId());
        blindBox.setOwnerAvatar(user.getAvatar());
        blindBox.setStock(blindBox.getTotal());
        blindBox.setSale(0);
        collectionRepo.save(blindBox);
        for (BlindBoxItem item : createBlindBox.getItems()) {
            Collection collection = list.stream().filter(i -> i.getId().equals(item.getCollectionId())).findAny()
                    .orElseThrow(new BusinessException("所选藏品不存在"));
            collection.setStock(collection.getStock() - item.getTotal());
            collectionRepo.save(collection);
            BlindBoxItem blindBoxItem = new BlindBoxItem();
            BeanUtils.copyProperties(collection, blindBoxItem);
            blindBoxItem.setId(null);
            blindBoxItem.setCollectionId(item.getCollectionId());
            blindBoxItem.setSale(0);
            blindBoxItem.setTotal(item.getTotal());
            blindBoxItem.setStock(item.getTotal());
            blindBoxItem.setRare(item.isRare());
            blindBoxItem.setBlindBoxId(blindBox.getId());
            blindBoxItem.setProjectId(blindBox.getProjectId());
            blindBoxItemRepo.save(blindBoxItem);
        }

        return blindBox;
    }


    public void appointment(Long id, Long userId) {
        Collection collection = collectionRepo.findById(id).orElseThrow(new BusinessException("资产无记录"));
        if (collection.getType() != CollectionType.BLIND_BOX) {
            throw new BusinessException("非盲盒，无需预约");
        }

        if (collection.getStartTime().isBefore(LocalDateTime.now())) {
            throw new BusinessException("盲盒已开售，无需预约");
        }

        appointmentRepo.save(Appointment.builder()
                .userId(userId)
                .blindBoxId(id)
                .build());
    }

    /**
     * 抽取盲盒
     *
     * @param collectionId
     * @return
     */
    @RedisLockable(prefix = RedisKeys.BLIND_DRAW_LOCK, key = "#collectionId", isWaiting = true, retryCount=10)
    public BlindBoxItem draw(Long collectionId) {
        List<BlindBoxItem> items = blindBoxItemRepo.findByBlindBoxId(collectionId);

        Map<BlindBoxItem, Range<Integer>> randomRange = new HashMap<>();
        int c = 0, sum = 0;
        for (BlindBoxItem item : items) {
            randomRange.put(item, Range.between(c, c + item.getStock()));
            c += item.getStock();
            sum += item.getStock();
        }

        log.info("开始抽卡，盲盒ID:{}, 库存数量：{}", collectionId, sum);
        int retry = 0;
        BlindBoxItem winItem = null;
        while (winItem == null) {
            retry++;
            int rand = RandomUtils.nextInt(0, sum + 1);
            for (Map.Entry<BlindBoxItem, Range<Integer>> entry : randomRange.entrySet()) {
                BlindBoxItem item = entry.getKey();
                Range<Integer> range = entry.getValue();
                if (rand >= range.getMinimum() && rand < range.getMaximum()) {
                    int total = items.stream().filter(i -> !i.isRare())
                            .mapToInt(BlindBoxItem::getTotal).sum();
                    int stock = items.stream().filter(i -> !i.isRare())
                            .mapToInt(BlindBoxItem::getStock).sum();
                    if (item.isRare()) {
                        double nRate = stock / (double) total;
                        double rRate = (item.getStock() - 1) / (double) item.getTotal();

                        if (Math.abs(nRate - rRate) < (1 / (double) item.getTotal()) || retry > 1 || rRate == 0) {
                            if (!(nRate > 0.1 && item.getStock() == 1)) {
                                winItem = item;
                            }
                        }
                    } else {
                        double nRate = (stock - 1) / (double) total;
                        double rRate = item.getStock() / (double) item.getTotal();

                        if (Math.abs(nRate - rRate) < 0.2 || retry > 1 || nRate == 0) {
                            winItem = item;
                        }
                    }
                }
            }

            if (retry > 100 && winItem == null) {
                throw new BusinessException("盲盒抽卡失败,剩余数量：" + sum);
            }
        }

        log.info("开始抽卡，盲盒ID:{}, 获得藏品ID：{}, 作品库存：{}", collectionId, winItem.getCollectionId(), winItem.getStock());
        //变更的是盲盒条目库存
        blindBoxItemRepo.decreaseStockAndIncreaseSale(winItem.getId(), 1);
        blindBoxItemRepo.flush();

        //检查作品库存
        Integer stock= blindBoxItemRepo.getStock(winItem.getId());
        if(stock < 0){
            throw new BusinessException("作品库存不足，抽取失败");
        }

        return winItem;
    }


    @Transactional
    public void addStock(Long id, int number) {
        Collection collection = collectionRepo.findByIdForUpdate(id).orElseThrow(new BusinessException("资产无记录"));
        if (collection.getSource() != CollectionSource.OFFICIAL) {
            throw new BusinessException("用户转售无法增发");
        }
        if (collection.getType() == CollectionType.BLIND_BOX) {
            throw new BusinessException("盲盒无法增发");
        }
        collection.setStock(collection.getStock() + number);
        collection.setTotal(collection.getTotal() + number);
    }

    //    @Scheduled(cron = "0 0 1 ?")
    public void delCollection() {
        List<Long> assetIds = assetRepo.findAllByStatus(AssetStatus.TRANSFERRED);
        List<Long> collections = collectionRepo.findAllByDelFalseAndAssetIdIn(assetIds);
        if (CollectionUtils.isEmpty(collections)) {
            return;
        }
        log.info("定时任务删除未删除藏品：{}", collections);
        collectionRepo.softDeleteByIdIn(collections);
    }

    /**
     * 统计次数
     *
     * @param id
     * @return
     */
    public Long increaseOrderStatis(Long id) {
        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.MQ_ORDER_STATIS + id);
        return ops.increment(1);
    }

    /**
     * 获取库存
     *
     * @param id
     * @return
     */
    public Integer getStockCache(Long id) {
        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.COLLECTION_STOCK + id);
        if (ops.get() == null) {
            Boolean success = ops.setIfAbsent(Optional.ofNullable(collectionRepo.getStock(id))
                    .orElse(0), 7, TimeUnit.DAYS);
            log.info("初始化库存信息：{}, 数量：{}, 返回结果：{}", id, ops.get(), success);
        }
        return (Integer) ops.get();
    }

    public Integer increaseStockCache(Long id, int number) {
        //细粒度锁定，提高并发
        synchronized (id.toString().intern()) {
            BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.COLLECTION_STOCK + id);
            if (ops.get() == null) {
                Boolean success = ops.setIfAbsent(Optional.ofNullable(collectionRepo.getStock(id))
                        .orElse(0), 7, TimeUnit.DAYS);
                log.info("初始化库存信息：{}, 数量：{}, 返回结果：{}", id, ops.get(), success);
            }
            Long stock = ops.increment(number);
            log.debug("库存变化，藏品id:{}, 数量：{}", id, stock);

            return stock.intValue();
        }

    }


    /**
     * 初始化藏品编号池
     *
     * @return
     */
    public void initCollectionNumberPool(Collection collection) {
        String redisKey = RedisKeys.COLLECTION_NUMBER_POOL + collection.getId();
        redisTemplate.delete(redisKey);
        if (StringUtils.isEmpty(collection.getPicPattern())) {
            return;
        }

        BoundListOperations<String, Object> ops = redisTemplate.boundListOps(redisKey);
        int lastNumber = 0;
        for (int i = 1; i <= collection.getTotal(); i++) {
            //检查是否已售
            Integer saleCount = assetRepo.findSaleAssetCount(collection.getId(), i);
            if (saleCount == 0) {
                ops.rightPush(i);
                lastNumber = i;
            }
        }
        //藏品序号增加已使用的编号数
        collectionRepo.increaseNumber(collection.getId(), lastNumber);
        log.info("初始化藏品编号池，collectionId:{}, 数量：{} ", collection.getId(), ops.size());

    }

    public BuyCollectionStatus checkBuyStatus(CollectionDTO dto) {
        //判断是否到贩卖时间
        boolean notStart = dto.getStartTime() != null && dto.getStartTime().isAfter(LocalDateTime.now());
        if (dto.isScheduleSale() && dto.isSalable() && notStart && !dto.isIsforwardBuying()) {
            return BuyCollectionStatus.COMING_SOON;
        }
        if (dto.isSalable() && dto.isIsforwardBuying()){
            return BuyCollectionStatus.FAST_PASS;
        }
        if (dto.isSalable() && dto.getStock() <= 0) {
            return CollectionSource.OFFICIAL.equals(dto.getSource())?BuyCollectionStatus.SOLD_OUT:BuyCollectionStatus.LOCK_ORDER;
        }
        if (dto.isSalable()){
            return BuyCollectionStatus.BUY;
        }
        return BuyCollectionStatus.ONLY_SHOW;
    }

    /**
     * 修改藏品假数据,非事务处理
     *
     * @param collection
     */
    @Transactional(Transactional.TxType.NEVER)
    public boolean mockLimitData(Collection collection) {
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        String key = RedisKeys.ASSET_SHELL_LIMIT_KEY + collection.getAssetId();
        String oldValue = (String) ops.get(key);
        if (oldValue != null) {
            collection.setSoldOut(1);
            collection.setStock(0);
            return true;
        }
        return false;
    }

    /**
     * 返回全部藏品数据放至service处理
     * @param user  用户
     * @param all 查询的藏品
     * @return
     */
    public Page<CollectionDTO> allCollection(User user, Page<Collection> all) {
        this.filter(all.getContent(), user);
        //查询提前优先购买时间
        Page<CollectionDTO> userDTOS = this.toDTO(all);
        for (CollectionDTO item : userDTOS) {
            //图片压缩
//            if (sysConfigService.getBoolean(SysConstants.PICTURE_COMPRESSION) && item.getPic() != null && !item.getPic().isEmpty()) {
//                for (FileObject fileObject : item.getPic()) {
//                    fileObject.setUrl(fileObject.getUrl() + "?x-oss-process=image/resize,h_300,m_lfit");
//                }
//            }
            if (user != null) {
                List<UserWareHouse> userWareHouses = this.getAdvancePurchaseTime(user.getId(), item);
                List<UserWareHouse> list = userWareHouses.stream().filter(userWareHouse -> Objects.requireNonNullElse(item.getStartTime(), LocalDateTime.now()).isBefore(LocalDateTime.now().plusHours(userWareHouse.getAdvancePurchaseTime()))).collect(Collectors.toList());
                item.setIsforwardBuying(!list.isEmpty());
                //假数据处理
                this.mockLimitData(item);
            }
            item.setBuyCollectionStatus(this.checkBuyStatus(item));
        }
        this.displayPriorityOrder(userDTOS.getContent());
        return userDTOS;
    }

    /**
     * 根据Id 查询藏品信息
     * @param id
     * @param user
     * @return
     */
    public CollectionDTO getById(String id,User user){
        Collection collection = collectionRepo.findById(Long.valueOf(id)).orElseThrow(new BusinessException("藏品无记录"));
        List<Collection> allList = new ArrayList<>();
        allList.add(collection);
        this.filter(allList, user);
        CollectionDTO collectionDTO = this.toDTO(collection, true);

        //显示缓存的库存
        Integer cacheStock = this.getStockCache(collectionDTO.getId());
        collectionDTO.setStock(cacheStock);

        if (user != null) {
            List<UserWareHouse> userWareHouses = this.getAdvancePurchaseTime(user.getId(), collection);
            List<UserWareHouse> list = userWareHouses.stream().filter(userWareHouse -> Objects.requireNonNullElse(collection.getStartTime(), LocalDateTime.now()).isBefore(LocalDateTime.now().plusHours(userWareHouse.getAdvancePurchaseTime()))).collect(Collectors.toList());
            collectionDTO.setIsforwardBuying(!list.isEmpty());
            //假数据处理

            //假数据处理
            collectionDTO.setImpose(this.mockLimitData(collectionDTO));
        }
        List<CollectionDTO> list = new ArrayList<>();
        list.add(collectionDTO);
        this.displayPriorityOrder(list);
        collectionDTO.setBuyCollectionStatus(this.checkBuyStatus(collectionDTO));
        return collectionDTO;
    }
}
