package com.izouma.nineth.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayClient;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.kevinsawicki.http.HttpRequest;
import com.izouma.nineth.TokenHistory;
import com.izouma.nineth.config.AlipayProperties;
import com.izouma.nineth.config.WxPayProperties;
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.*;
import com.izouma.nineth.event.CreateAssetEvent;
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.SnowflakeIdWorker;
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.codec.EncoderException;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;

import javax.transaction.Transactional;
import java.io.File;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
@AllArgsConstructor
@Slf4j
public class AssetService {

    private AssetRepo          assetRepo;
    private UserRepo           userRepo;
    private NFTService         nftService;
    private CollectionRepo     collectionRepo;
    private ApplicationContext applicationContext;
    private OrderRepo          orderRepo;
    private SysConfigService   sysConfigService;
    private GiftOrderRepo      giftOrderRepo;
    private TokenHistoryRepo   tokenHistoryRepo;
    private AlipayProperties   alipayProperties;
    private AlipayClient       alipayClient;
    private WxPayProperties    wxPayProperties;
    private WxPayService       wxPayService;
    private Environment        env;

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

    public Asset createAsset(Order order) {
        User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在"));
        Collection collection = collectionRepo.findById(order.getCollectionId()).orElseThrow(new BusinessException("藏品不存在"));
        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())
                .privileges(collection.getPrivileges())
                .category(order.getCategory())
                .canResale(order.isCanResale())
                .royalties(order.getRoyalties())
                .serviceCharge(order.getServiceCharge())
                .price(order.getPrice())
                .status(AssetStatus.NORMAL)
                .owner(user.getNickname())
                .ownerId(user.getId())
                .ownerAvatar(user.getAvatar())
                .build();
        assetRepo.save(asset);

        tokenHistoryRepo.save(TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(order.getMinter())
                .fromUserId(order.getMinterId())
                .toUser(user.getNickname())
                .toUserId(user.getId())
                .operation("出售")
                .price(order.getPrice())
                .build());

        return asset;
    }

    public Asset createAsset(Order order, BlindBoxItem winItem) {
        User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在"));
        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.getPic())
                .properties(winItem.getProperties())
                .privileges(winItem.getPrivileges())
                .canResale(winItem.isCanResale())
                .royalties(winItem.getRoyalties())
                .serviceCharge(winItem.getServiceCharge())
                .price(order.getPrice())
                .status(AssetStatus.NORMAL)
                .ipfsUrl(ipfsUpload(winItem.getPic().get(0).getUrl()))
                .build();
        assetRepo.save(asset);
        tokenHistoryRepo.save(TokenHistory.builder()
                .tokenId(asset.getTokenId())
                .fromUser(order.getMinter())
                .fromUserId(order.getMinterId())
                .toUser(user.getNickname())
                .toUserId(user.getId())
                .operation("出售")
                .price(order.getPrice())
                .build());

        return asset;
    }

    public void mint(Asset asset) {
        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);
        }
        try {
            NFT nft = nftService.createToken(user.getNftAccount());
            if (nft != null) {
                asset.setTokenId(nft.getTokenId());
                asset.setBlockNumber(nft.getBlockNumber());
                asset.setTxHash(nft.getTxHash());
                asset.setGasUsed(nft.getGasUsed());
                asset.setIpfsUrl(ipfsUpload(asset.getPic().get(0).getUrl()));
                assetRepo.save(asset);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        applicationContext.publishEvent(new CreateAssetEvent(this, true, asset));
    }

    public String ipfsUpload(String url) {
        try {
            IPFS ipfs = new IPFS("112.74.34.84", 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;
        }
        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)
                .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)
                .build();
        collectionRepo.save(collection);
        asset.setPublicShow(true);
        asset.setConsignment(true);
        asset.setPublicCollectionId(collection.getId());
        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);
    }

    @Transactional
    public GiftOrder gift(Long userId, Long assetId, Long toUserId) {
        Asset asset = assetRepo.findById(assetId).orElseThrow(new BusinessException("资产不存在"));
        if (!(asset.getStatus() == AssetStatus.NORMAL)) {
            throw new BusinessException("当前状态不可转赠");
        }
        if (asset.isConsignment()) {
            throw new BusinessException("请先取消寄售");
        }
        if (asset.isPublicShow()) {
            cancelPublic(asset);
        }

        asset.setStatus(AssetStatus.GIFTING);
        assetRepo.save(asset);

        GiftOrder giftOrder = GiftOrder.builder()
                .userId(userId)
                .assetId(assetId)
                .toUserId(toUserId)
                .gasPrice(sysConfigService.getBigDecimal("gas_fee"))
                .status(OrderStatus.NOT_PAID)
                .build();
        return giftOrderRepo.save(giftOrder);
    }

    public void payOrderAlipay(Long id, Model model) {
        try {
            GiftOrder order = giftOrderRepo.findById(id).orElseThrow(new BusinessException("订单不存在"));

            if (order.getStatus() != OrderStatus.NOT_PAID) {
                throw new BusinessException("订单状态错误");
            }

            JSONObject bizContent = new JSONObject();
            bizContent.put("notifyUrl", alipayProperties.getNotifyUrl());
            bizContent.put("returnUrl", alipayProperties.getReturnUrl());
            bizContent.put("out_trade_no", String.valueOf(new SnowflakeIdWorker(0, 0).nextId()));
            bizContent.put("total_amount", order.getGasPrice().stripTrailingZeros().toPlainString());
            bizContent.put("disable_pay_channels", "pcredit,creditCard");
            if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
                // 测试环境设为1分
                bizContent.put("total_amount", "0.01");
            }
            bizContent.put("subject", "转赠GAS费");
            bizContent.put("product_code", "QUICK_WAP_PAY");
            JSONObject body = new JSONObject();
            body.put("action", "payGiftOrder");
            body.put("userId", order.getUserId());
            body.put("orderId", order.getId());
            bizContent.put("body", body.toJSONString());

            AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
            alipayRequest.setReturnUrl(alipayProperties.getReturnUrl());
            alipayRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
            alipayRequest.setBizContent(JSON.toJSONString(bizContent));

            String form = alipayClient.pageExecute(alipayRequest).getBody();
            model.addAttribute("form", form);
        } catch (BusinessException err) {
            model.addAttribute("errMsg", err.getError());
        } catch (Exception e) {
            model.addAttribute("errMsg", e.getMessage());
        }
    }

    public Object payOrderWeixin(Long id, String tradeType, String openId) throws WxPayException, EncoderException {
        GiftOrder order = giftOrderRepo.findById(id).orElseThrow(new BusinessException("订单不存在"));
        if (order.getStatus() != OrderStatus.NOT_PAID) {
            throw new BusinessException("订单状态错误");
        }

        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
        request.setBody("转赠GAS费");
        request.setOutTradeNo(String.valueOf(new SnowflakeIdWorker(1, 1).nextId()));
        request.setTotalFee(order.getGasPrice().multiply(BigDecimal.valueOf(100)).intValue());
        if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
            // 测试环境设为1分
//                    request.setTotalFee(1);
        }
        request.setSpbillCreateIp("180.102.110.170");
        request.setNotifyUrl(wxPayProperties.getNotifyUrl());
        request.setTradeType(tradeType);
        request.setOpenid(openId);
        request.setSignType("MD5");
        JSONObject body = new JSONObject();
        body.put("action", "payGiftOrder");
        body.put("userId", order.getUserId());
        body.put("orderId", order.getId());
        request.setAttach(body.toJSONString());

        if (WxPayConstants.TradeType.MWEB.equals(tradeType)) {
            WxPayMwebOrderResult result = wxPayService.createOrder(request);
            return result.getMwebUrl() + "&redirect_url=" + new URLCodec().encode(wxPayProperties.getReturnUrl());
        } else if (WxPayConstants.TradeType.JSAPI.equals(tradeType)) {
            return wxPayService.<WxPayMpOrderResult>createOrder(request);
        }
        throw new BusinessException("不支持此付款方式");
    }

    @Transactional
    public void giftNotify(Long orderId, PayMethod payMethod, String transactionId) {
        GiftOrder giftOrder = giftOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
        Asset asset = assetRepo.findById(giftOrder.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
        User newOwner = userRepo.findById(giftOrder.getToUserId()).orElseThrow(new BusinessException("用户不存在"));

        giftOrder.setPayMethod(payMethod);
        giftOrder.setStatus(OrderStatus.FINISH);
        giftOrder.setTransactionId(transactionId);
        giftOrder.setPayTime(LocalDateTime.now());
        giftOrder.setPayMethod(PayMethod.ALIPAY);

        transfer(asset, newOwner);

        tokenHistoryRepo.save(TokenHistory.builder()
                .fromUser(asset.getOwner())
                .fromUserId(asset.getOwnerId())
                .toUser(newOwner.getNickname())
                .toUserId(newOwner.getId())
                .operation("转赠")
                .build());
    }

    public void transfer(Asset asset, User toUser) {
        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.setPublicCollectionId(null);
        newAsset.setStatus(AssetStatus.NORMAL);
        assetRepo.save(newAsset);

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

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