package com.izouma.nineth.service;

import com.izouma.nineth.TokenHistory;
import com.izouma.nineth.domain.*;
import com.izouma.nineth.dto.PageQuery;
import com.izouma.nineth.enums.AssetStatus;
import com.izouma.nineth.enums.CollectionSource;
import com.izouma.nineth.enums.CollectionType;
import com.izouma.nineth.enums.OrderStatus;
import com.izouma.nineth.event.TransferAssetEvent;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.lock.RedisLockable;
import com.izouma.nineth.repo.*;
import com.izouma.nineth.utils.JpaUtils;
import com.izouma.nineth.utils.SecurityUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

@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 AssetMintService   assetMintService;

    public Page<Asset> all(PageQuery pageQuery) {
        return assetRepo.findAll(JpaUtils.toSpecification(pageQuery, Asset.class), JpaUtils.toPageRequest(pageQuery));
    }

    @Async
    public Future<Asset> createAsset(Collection collection, User user, Long orderId, BigDecimal price, String type, Integer number) {
        Asset asset = Asset.create(collection, user);
        asset.setNumber(number);
        asset.setOrderId(orderId);
        asset.setPrice(price);
        asset.setIpfsUrl(assetMintService.ipfsUpload(collection.getPic().get(0).getUrl()));
        assetRepo.save(asset);

        TokenHistory tokenHistory = tokenHistoryRepo.save(TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(collection.getMinter())
                .fromUserId(collection.getMinterId())
                .toUser(user.getNickname())
                .toUserId(user.getId())
                .operation(type)
                .price(price)
                .build());
        assetMintService.mint(asset, tokenHistory.getId());
        return new AsyncResult<>(asset);
    }

    @Async
    public Future<Asset> createAsset(BlindBoxItem winItem, User user, Long orderId, BigDecimal price, String type, Integer number) {
        Asset asset = Asset.create(winItem, user);
        asset.setNumber(number);
        asset.setOrderId(orderId);
        asset.setPrice(price);
        asset.setIpfsUrl(assetMintService.ipfsUpload(winItem.getPic().get(0).getUrl()));
        assetRepo.save(asset);
        TokenHistory tokenHistory = tokenHistoryRepo.save(TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(winItem.getMinter())
                .fromUserId(winItem.getMinterId())
                .toUser(user.getNickname())
                .toUserId(user.getId())
                .operation(type)
                .price(price)
                .build());
        assetMintService.mint(asset, tokenHistory.getId());
        return new AsyncResult<>(asset);
    }


    public void publicShow(Long id) {
        Asset asset = assetRepo.findById(id).orElseThrow(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 void consignment(Long id, BigDecimal price) {
        Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        if (asset.isConsignment()) {
            throw new BusinessException("已寄售，请勿重新操作");
        }
        if (asset.getStatus() != AssetStatus.NORMAL) {
            throw new BusinessException("当前状态不可寄售");
        }
        if (asset.isPublicShow()) {
            cancelPublic(asset);
        }
        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(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("无记录"));
        cancelConsignment(asset);
    }

    public void cancelConsignment(Asset asset) {
        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("无记录"));
        cancelPublic(asset);
    }

    public void cancelPublic(Asset asset) {
        if (!asset.isPublicShow()) {
            return;
        }
        if (asset.isConsignment()) {
            cancelConsignment(asset);
        }
        Collection collection = collectionRepo.findById(asset.getPublicCollectionId())
                .orElseThrow(new BusinessException("无展示记录"));
        collectionRepo.delete(collection);

        asset.setPublicShow(false);
        asset.setCollectionId(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 -> {
            p.setOpened(true);
            p.setOpenTime(LocalDateTime.now());
            p.setOpenedBy(SecurityUtils.getAuthenticatedUser().getId());
        });
        assetRepo.save(asset);
    }

    @Async
    public void transfer(Asset asset, BigDecimal price, User toUser, String reason, Long orderId) {
        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);
        assetRepo.save(newAsset);

        asset.setPublicShow(false);
        asset.setConsignment(false);
        asset.setPublicCollectionId(null);
        asset.setStatus(AssetStatus.GIFTED);
        asset.setOwner(toUser.getNickname());
        asset.setOwnerId(toUser.getId());
        asset.setOwnerAvatar(toUser.getAvatar());
        assetRepo.save(asset);

        tokenHistoryRepo.save(TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(asset.getOwner())
                .fromUserId(asset.getOwnerId())
                .toUser(toUser.getNickname())
                .toUserId(toUser.getId())
                .operation(reason)
                .build());

        if (orderId != null) {
            applicationContext.publishEvent(new TransferAssetEvent(this, true, newAsset));
        }
    }

    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);
    }

    @RedisLockable(key = "#id", expiration = 60, isWaiting = true)
    public void testLock(String id, String i) throws InterruptedException {
        Thread.sleep(1000);
        log.info("" + i);
    }
}
