package com.izouma.nineth.service;

import com.github.kevinsawicki.http.HttpRequest;
import com.izouma.nineth.domain.*;
import com.izouma.nineth.dto.NFT;
import com.izouma.nineth.dto.NFTAccount;
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.event.CreateAssetEvent;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.repo.AssetRepo;
import com.izouma.nineth.repo.CollectionRepo;
import com.izouma.nineth.repo.UserRepo;
import com.izouma.nineth.utils.JpaUtils;
import io.ipfs.api.IPFS;
import io.ipfs.api.MerkleNode;
import io.ipfs.api.NamedStreamable;
import io.ipfs.multihash.Multihash;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.File;
import java.math.BigDecimal;

@Service
@AllArgsConstructor
@Slf4j
public class AssetService {

    private AssetRepo          assetRepo;
    private UserRepo           userRepo;
    private NFTService         nftService;
    private CollectionRepo     collectionRepo;
    private ApplicationContext applicationContext;

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

    @Async
    public void createAsset(Order order) {
        User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在"));
        if (StringUtils.isEmpty(user.getPublicKey())) {
            NFTAccount account = nftService.createAccount(user.getUsername());
            user.setNftAccount(account.getAccountId());
            user.setKmsId(account.getAccountKmsId());
            user.setPublicKey(account.getPublicKey());
            userRepo.save(user);
        }
        try {
            NFT nft = nftService.createToken(user.getNftAccount());
            String ipfsUrl = ipfsUpload(order.getPic().get(0).getUrl());
            if (nft != null) {
                Asset asset = Asset.builder()
                        .userId(user.getId())
                        .orderId(order.getId())
                        .collectionId(order.getCollectionId())
                        .minter(order.getMinter())
                        .minterId(order.getMinterId())
                        .minterAvatar(order.getMinterAvatar())
                        .name(order.getName())
                        .detail(order.getDetail())
                        .pic(order.getPic())
                        .properties(order.getProperties())
                        .category(order.getCategory())
                        .canResale(order.isCanResale())
                        .royalties(order.getRoyalties())
                        .serviceCharge(order.getServiceCharge())
                        .tokenId(nft.getTokenId())
                        .blockNumber(nft.getBlockNumber())
                        .txHash(nft.getTxHash())
                        .gasUsed(nft.getGasUsed())
                        .price(order.getPrice())
                        .status(AssetStatus.NORMAL)
                        .ipfsUrl(ipfsUrl)
                        .number((int) (assetRepo.countByIpfsUrlAndStatusNot(ipfsUrl, AssetStatus.TRANSFERRED) + 1))
                        .build();
                assetRepo.save(asset);
                applicationContext.publishEvent(new CreateAssetEvent(this, true, order, asset));
                return;
            }
        } catch (Exception e) {
            log.error("创建token失败", e);
        }
        applicationContext.publishEvent(new CreateAssetEvent(this, false, order, null));
    }

    @Async
    public void createAsset(Order order, BlindBoxItem winItem) {
        User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在"));
        if (StringUtils.isEmpty(user.getPublicKey())) {
            NFTAccount account = nftService.createAccount(user.getUsername());
            user.setNftAccount(account.getAccountId());
            user.setKmsId(account.getAccountKmsId());
            user.setPublicKey(account.getPublicKey());
            userRepo.save(user);
        }
        try {
            NFT nft = nftService.createToken(user.getNftAccount());
            if (nft != null) {
                Asset asset = Asset.builder()
                        .userId(user.getId())
                        .orderId(order.getId())
                        .collectionId(order.getCollectionId())
                        .minter(winItem.getMinter())
                        .minterId(winItem.getMinterId())
                        .minterAvatar(winItem.getMinterAvatar())
                        .name(winItem.getName())
                        .detail(winItem.getDetail())
                        .pic(winItem.getPics())
                        .properties(winItem.getProperties())
                        .canResale(winItem.isCanResale())
                        .royalties(winItem.getRoyalties())
                        .serviceCharge(winItem.getServiceCharge())
                        .tokenId(nft.getTokenId())
                        .blockNumber(nft.getBlockNumber())
                        .txHash(nft.getTxHash())
                        .gasUsed(nft.getGasUsed())
                        .price(order.getPrice())
                        .status(AssetStatus.NORMAL)
                        .ipfsUrl(ipfsUpload(winItem.getPics().get(0).getUrl()))
                        .build();
                assetRepo.save(asset);
                applicationContext.publishEvent(new CreateAssetEvent(this, true, order, asset));
                return;
            }
        } catch (Exception e) {
            log.error("创建token失败", e);
        }
        applicationContext.publishEvent(new CreateAssetEvent(this, false, order, null));
    }

    public String ipfsUpload(String url) {
        try {
            IPFS ipfs = new IPFS("121.40.132.44", 5001);
            HttpRequest request = HttpRequest.get(url);
            File file = File.createTempFile("ipfs", ".tmp");
            request.receive(file);
            NamedStreamable.FileWrapper file1 = new NamedStreamable.FileWrapper(file);
            MerkleNode put = ipfs.add(file1).get(0);
            Multihash multihash = ipfs.pin.add(put.hash).get(0);
            log.info("上传ipfs成功 {}", multihash.toBase58());
            return multihash.toBase58();
        } catch (Exception e) {
        }
        return null;
    }

    public void publicShow(Long id) {
        Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        if (asset.isPublicShow()) {
            return;
        }
        User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
        Collection collection = Collection.builder()
                .name(asset.getName())
                .pics(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())
                .build();
        collectionRepo.save(collection);
        asset.setPublicShow(true);
        asset.setPublicCollectionId(collection.getId());
    }

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

        asset.setPublicShow(false);
        asset.setCollectionId(null);
        assetRepo.save(asset);
    }
}
