package com.fzm.mall.server.admin.user.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fzm.mall.server.admin.block_chain.util.ChainUtil;
import com.fzm.mall.server.admin.constant.MALLGlobalConfig;
import com.fzm.mall.server.admin.constant.MallResponseError;
import com.fzm.mall.server.admin.exception.MyException;
import com.fzm.mall.server.admin.goods_center.entity.vo.GoodsSpuVO;
import com.fzm.mall.server.admin.goods_center.mapper.GoodsSkuUsufructMapper;
import com.fzm.mall.server.admin.goods_center.mapper.SkuMapper;
import com.fzm.mall.server.admin.goods_center.mapper.SpuMapper;
import com.fzm.mall.server.admin.goods_center.model.GoodsSkuUsufruct;
import com.fzm.mall.server.admin.goods_center.model.Sku;
import com.fzm.mall.server.admin.goods_center.model.Spu;
import com.fzm.mall.server.admin.home_page.model.Merchant;
import com.fzm.mall.server.admin.home_page.service.IMerchantService;
import com.fzm.mall.server.admin.mq.MsgProducer;
import com.fzm.mall.server.admin.mq.enums.QueueTTLTypeEnum;
import com.fzm.mall.server.admin.order.entity.dto.TranDto;
import com.fzm.mall.server.admin.order.mapper.PayMapper;
import com.fzm.mall.server.admin.order.model.OrderDetail;
import com.fzm.mall.server.admin.order.model.Pay;
import com.fzm.mall.server.admin.pay.dto.WxPayRefundDTO;
import com.fzm.mall.server.admin.user.mapper.AssetMapper;
import com.fzm.mall.server.admin.user.model.Asset;
import com.fzm.mall.server.admin.user.service.IAssetService;
import com.fzm.mall.server.admin.user_center.model.User;
import com.fzm.mall.server.admin.user_center.service.IUserService;
import com.fzm.mall.server.admin.util.WxPayUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * <p>
 * 用户资产信息表 服务实现类
 * </p>
 *
 * @author fzm
 * @since 2021-04-12
 */
@Service
@Transactional
public class AssetServiceImpl extends ServiceImpl<AssetMapper, Asset> implements IAssetService {

    @Value("${pay.pay-in-chain}")
    private Boolean payInChain;

    @Autowired
    private AssetMapper assetMapper;
    @Autowired
    private IUserService userService;
    @Autowired
    private IMerchantService merchantService;
    @Autowired
    private ChainUtil chainUtil;
    @Autowired
    private MsgProducer producer;
    @Autowired
    private SpuMapper spuMapper;
    @Autowired
    private PayMapper payMapper;
    @Autowired
    private WxPayUtil wxPayUtil;
    @Autowired
    private GoodsSkuUsufructMapper goodsSkuUsufructMapper;
    @Autowired
    private SkuMapper skuMapper;

    /**
     * 商品订单退款-积分还原，商品币还原
     * <p>
     * 审核通过：1.币：根据order_detail的coin和number，将user_asset对应的冻结数量frozen扣除，通过链上方法还给商家
     * 2.积分：根据order_info的amount，将积分还给买家
     * <p>
     * 审核不通过：
     * 1.币：根据order_detail的coin和number，将user_asset对应的冻结数量frozen扣除，加到可用资产amount
     *
     * @param uid
     * @param amount
     * @param merchantId
     * @param orderDetailList
     * @param isApproved
     */
    @Override
    public void integralDeduction(String oid, String uid, BigDecimal amount, String merchantId, List<OrderDetail> orderDetailList, Boolean isApproved) {
        orderDetailCoinRefund(uid, merchantId, orderDetailList, isApproved);

        Pay pay = payMapper.readByOid(oid);
        switch (pay.getPayType()) {
            case 4:
                //积分支付
                intergralRefund(uid, amount, isApproved);
                break;
            case 2:
                //微信支付
                wxPayRefund(pay.getPaidAmount(), pay.getOutBizNo(), isApproved);
                break;
        }
    }

    @Override
    public void refundCoinOnChain(int tableType, String tableId, String merchantId, String coin, String skuId, int number, String serialNo, String fromAddr, String toAddr) {
        QueryWrapper<Sku> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("sku_id", skuId);
        Sku sku = skuMapper.selectOne(queryWrapper);

        boolean isSalesNFTCopy = sku.getNftSalesType().equals(Spu.NFT_SALES_TYPE_COPY);

        /**
         * 只有订单退款时，买家将商品币退还商家，商家刷一下退回的币的编号为可用
         */
        boolean updateSkuSerialNo = TranDto.TABLE_TYPE_ORDER_INFO.equals(tableType);
        /**
         * NFT商品为“使用权销售时”，根据订单购买的编号重置为未使用
         */
        List<Integer> serialNoList = JSON.parseArray(serialNo, int.class);
        if (updateSkuSerialNo) {
            updateSkuSerialNo(serialNoList, merchantId, coin, isSalesNFTCopy);
        }
        //根据开关判断是否走链，默认不走链
        if (payInChain) {
            /**
             * NFT商品为“使用权销售”，tokenId+编号，循环转账操作
             */
            if (isSalesNFTCopy) {
                serialNoList.forEach(index -> {
                    Long copyTokenId = Long.valueOf(index);
                    payInChain(tableType, tableId, fromAddr, toAddr, coin, 1, copyTokenId);
                });
            } else {
                Long tokenId = Long.valueOf(coin.replace(MALLGlobalConfig.NFT_PREFIX, ""));
                payInChain(tableType, tableId, fromAddr, toAddr, coin, number, tokenId);
            }
        }
    }

    /**
     * 微信退款
     *
     * @param paidAmount 金额
     * @param outBizNo   原支付交易对应的微信订单号
     */
    private void wxPayRefund(BigDecimal paidAmount, String outBizNo, Boolean isApproved) {
        if (!isApproved) {
            return;
        }
        WxPayRefundDTO data = new WxPayRefundDTO();
        data.init(paidAmount, outBizNo);

        String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
        String bodyJsonString = JSONObject.toJSONString(data);
        wxPayUtil.doHttpPost(url, bodyJsonString);
    }

    /**
     * 审核通过：积分：根据order_info的amount，将积分还给买家
     * <p>
     * 审核不通过：不操作积分
     *
     * @param uid
     * @param amount
     * @param isApproved
     */
    private void intergralRefund(String uid, BigDecimal amount, Boolean isApproved) {
        if (!isApproved) {
            return;
        }
        QueryWrapper<Asset> queryWrapper = new QueryWrapper<Asset>().eq("uid", uid).eq("coin", MALLGlobalConfig.PLATFORM_BASE_INTEGRAL);
        Asset asset = assetMapper.selectOne(queryWrapper);
        asset.setAmount(asset.getAmount().add(amount));

        int update = assetMapper.update(asset, new UpdateWrapper<Asset>().eq("uid", uid).eq("coin", MALLGlobalConfig.PLATFORM_BASE_INTEGRAL));
        if (update == 0) {
            throw new MyException(MallResponseError.UPDATE_FAIL);
        }
    }


    /**
     * 审核通过：1.币：根据order_detail的coin和number，将user_asset对应的冻结数量frozen扣除，通过链上方法还给商家
     * 审核不通过：
     * 1.币：根据order_detail的coin和number，将user_asset对应的冻结数量frozen扣除，加到可用资产amount
     *
     * @param uid
     * @param merchantId
     * @param orderDetailList
     * @param isApproved
     */
    private void orderDetailCoinRefund(String uid, String merchantId, List<OrderDetail> orderDetailList, Boolean isApproved) {
        if (isApproved) {
            User user = userService.getOne(new QueryWrapper<User>().eq("uid", uid));
            Merchant merchant = merchantService.getOne(new QueryWrapper<Merchant>().eq("merchant_id", merchantId));
            orderDetailList.forEach(orderDetail -> {
                QueryWrapper<Asset> queryWrapper = new QueryWrapper<Asset>().eq("uid", uid).eq("coin", orderDetail.getCoin());
                Asset asset = assetMapper.selectOne(queryWrapper);
                BigDecimal frozen = asset.getFrozen();
                BigDecimal unfreezeNum = new BigDecimal(orderDetail.getNumber());
                BigDecimal result = frozen.subtract(unfreezeNum);
                frozen = result.compareTo(BigDecimal.ZERO) > 0 ? result : BigDecimal.ZERO;
                asset.setFrozen(frozen);

                int update = assetMapper.update(asset, new UpdateWrapper<Asset>().eq("uid", uid).eq("coin", orderDetail.getCoin()));
                if (update == 0) {
                    throw new MyException(MallResponseError.UPDATE_FAIL);
                }
                refundCoinOnChain(TranDto.TABLE_TYPE_ORDER_INFO, orderDetail.getOid(), merchantId, orderDetail.getCoin(), orderDetail.getSkuId(), orderDetail.getNumber(), orderDetail.getSerialNo(), user.getAddress(), merchant.getTokenIssueAddr());
            });
        } else {
            orderDetailList.forEach(orderDetail -> {
                QueryWrapper<Asset> queryWrapper = new QueryWrapper<Asset>().eq("uid", uid).eq("coin", orderDetail.getCoin());
                Asset asset = assetMapper.selectOne(queryWrapper);
                BigDecimal frozen = asset.getFrozen();
                BigDecimal unfreezeNum = new BigDecimal(orderDetail.getNumber());
                BigDecimal result = frozen.subtract(unfreezeNum);
                frozen = result.compareTo(BigDecimal.ZERO) > 0 ? result : BigDecimal.ZERO;
                asset.setFrozen(frozen);

                asset.setAmount(asset.getAmount().add(unfreezeNum));

                int update = assetMapper.update(asset, new UpdateWrapper<Asset>().eq("uid", uid).eq("coin", orderDetail.getCoin()));
                if (update == 0) {
                    throw new MyException(MallResponseError.UPDATE_FAIL);
                }
            });
        }
    }

    private void payInChain(int tableType, String tableId, String fromAddr, String toAddr, String coin, int number, Long tokenId) {
        TranDto tranDto = new TranDto();
        tranDto.setFromAddr(fromAddr);
        tranDto.setToAddr(toAddr);
        tranDto.setCoin(coin);
        //一个商品对应一个币，此处构造不存在精度丢失问题
        tranDto.setAmount(new BigDecimal(number));
        tranDto.setTableId(tableId);
        tranDto.setTableType(tableType);
        tranDto.setTokenId(tokenId);
        String hash;
        /**
         * 一幕所有的nft都是内部nft
         */

        boolean doEvmExec = false;
        if (doEvmExec) {
            long[] tokenIds = new long[]{tokenId};
            long[] amounts = new long[]{tranDto.getAmount().longValue()};
            hash = chainUtil.evmExec(tokenIds, amounts, tranDto.getFromAddr(), tranDto.getToAddr());
            tranDto.setCoinType(TranDto.COIN_TYPE_NFT_OUT);
        } else {
            /**
             * 内部nft操作
             */
            hash = chainUtil.evmTransfer(tranDto.getToAddr(), tranDto.getFromAddr(), tokenId, tranDto.getAmount().longValue());
            tranDto.setCoinType(TranDto.COIN_TYPE_NFT_IN);
        }
        tranDto.setHash(hash);
        producer.sendTTL(JSON.toJSONString(tranDto), QueueTTLTypeEnum.ORDER_REFUND_COINS_TTL, 5 * 1000);
    }

    /**
     * 商品为“使用权”时，退款和退货，需要重置已使用的编号
     *
     * @param serialNoList
     * @param merchantId
     * @param coin
     * @param isSalesNFTCopy
     */
    private void updateSkuSerialNo(List<Integer> serialNoList, String merchantId, String coin, boolean isSalesNFTCopy) {
        if (!isSalesNFTCopy || CollectionUtils.isEmpty(serialNoList)) {
            return;
        }
        QueryWrapper<GoodsSkuUsufruct> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("prefix", coin)
                .eq("merchant_id", merchantId);
        GoodsSkuUsufruct skuUsufruct = goodsSkuUsufructMapper.selectOne(queryWrapper);
        if (skuUsufruct == null) {
            return;
        }
        List<Integer> dataSerialNoList = JSON.parseArray(skuUsufruct.getSerialNo(), int.class);

        serialNoList.forEach(index -> {
            dataSerialNoList.add(index);
        });
        Collections.sort(dataSerialNoList, Comparator.naturalOrder());
        skuUsufruct.setSerialNo(JSON.toJSONString(dataSerialNoList));

        UpdateWrapper<GoodsSkuUsufruct> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("prefix", coin)
                .eq("merchant_id", merchantId)
                .eq("update_time", skuUsufruct.getUpdateTime());

        int update = goodsSkuUsufructMapper.update(skuUsufruct, updateWrapper);
        if (update == 0) {
            throw new MyException(MallResponseError.UPDATE_FAIL);
        }
    }
}
