package com.izouma.nineth.service;

import com.github.kevinsawicki.http.HttpRequest;
import com.izouma.nineth.config.IPFSProperties;
import com.izouma.nineth.config.RedisKeys;
import com.izouma.nineth.domain.Asset;
import com.izouma.nineth.domain.Order;
import com.izouma.nineth.domain.User;
import com.izouma.nineth.dto.NFT;
import com.izouma.nineth.dto.NFTAccount;
import com.izouma.nineth.enums.AssetStatus;
import com.izouma.nineth.enums.OrderStatus;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.lock.RedisLockable;
import com.izouma.nineth.repo.AssetRepo;
import com.izouma.nineth.repo.OrderRepo;
import com.izouma.nineth.repo.UserRepo;
import com.izouma.nineth.utils.ApplicationContextUtil;
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.stereotype.Service;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import java.io.File;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Set;

@Service
@Slf4j
@AllArgsConstructor
public class AssetMintService {
    private AssetRepo          assetRepo;
    private UserRepo           userRepo;
    private OrderRepo          orderRepo;
    private NFTService         nftService;

    private IPFSProperties ipfsProperties;

    private EntityManager entityManager;

    /**
     * 铸造，由队列调用，不需要异步
     *
     * 锁定资产，防止重复铸造
     * @param assetId
     */
    @RedisLockable(prefix = RedisKeys.MINT_ASSET_LOCK, key="#assetId")
    @Transactional(rollbackOn = Exception.class)
    public void mint(Long assetId) throws Exception{
        Asset asset = assetRepo.findById(assetId).orElse(null);
        if(asset == null){
            log.warn("资产不存在，制造失败：" + assetId);
            return;
        }

        if(asset.getStatus() != AssetStatus.NORMAL){
            throw new BusinessException("资产状态不正确");
        }

        if(asset.getTxHash() != null){
            throw new BusinessException("资产已铸造过，不能重复铸造");
        }

        //自动创建nft账户和上链
        User user = userRepo.findById(asset.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);
        }

        NFT nft = nftService.createToken(user.getNftAccount(), asset.getTokenId());

        asset.setTokenId(nft.getTokenId());
        asset.setBlockNumber(nft.getBlockNumber());
        asset.setTxHash(nft.getTxHash());
        asset.setGasUsed(nft.getGasUsed());
        //txHash 为1，表示是测试数据
//        if (asset.getIpfsUrl() == null) {
//            String ipfsUrl = ipfsUpload(asset.getId(), asset.getPic().get(0).getUrl());
//            asset.setIpfsUrl(ipfsUrl);
//        }

        //assetRepo.saveAndFlush(asset);
        //防止修改了资产状态
        entityManager.detach(asset);
        assetRepo.updateMintInfo(asset);

        //直接更新订单，防止异步操作的资产数据不同步
        try {
            if(asset.getOrderId() != null) {
                Order order = orderRepo.findById(asset.getOrderId()).orElseThrow(new BusinessException("订单不存在"));
                order.setTxHash(asset.getTxHash());
                order.setGasUsed(asset.getGasUsed());
                order.setBlockNumber(asset.getBlockNumber());
                order.setStatus(OrderStatus.FINISH);
                orderRepo.save(order);
                log.error("成功更新订单状态,orderId:{}", asset.getOrderId());
            }
        }catch(Exception e){
            log.error("更新订单状态失败,orderId:" +  asset.getOrderId(), e);
        }

    }

    public String ipfsUpload(Long assetId, String url) {
        String serverUrl = ipfsProperties.getHost() + ":" +  ipfsProperties.getPort();
        log.info("开始上传ipfs("+serverUrl+"), assetId:" +  assetId);
        File resourceFile = null;
        try {
            IPFS ipfs = new IPFS(ipfsProperties.getHost(), ipfsProperties.getPort());
            HttpRequest request = HttpRequest.get(url);
            resourceFile = File.createTempFile("ipfs", ".tmp");
            request.receive(resourceFile);
            NamedStreamable.FileWrapper file1 = new NamedStreamable.FileWrapper(resourceFile);
            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) {
            log.error("上传ipfs失败, assetId:" +  assetId, e);
        }finally{
            try{
                if(resourceFile != null){
                    resourceFile.delete();
                }
            }catch(Exception e){
                //ignore
            }
        }

        return null;
    }
}
