package com.fzm.mall.server.front.asset.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fzm.mall.server.front.asset.mapper.UserAssetMapper;
import com.fzm.mall.server.front.asset.mapper.UserAssetTransferRecordMapper;
import com.fzm.mall.server.front.asset.model.UserAsset;
import com.fzm.mall.server.front.asset.model.UserAssetTransferRecord;
import com.fzm.mall.server.front.asset.model.dto.UserAssetTransferRecordSaveDTO;
import com.fzm.mall.server.front.asset.model.vo.UserAssetTransferRecordVO;
import com.fzm.mall.server.front.asset.service.IUserAssetTransferRecordService;
import com.fzm.mall.server.front.enums.QueueTTLTypeEnum;
import com.fzm.mall.server.front.goods.mapper.GoodSpuMapper;
import com.fzm.mall.server.front.goods.model.GoodSpu;
import com.fzm.mall.server.front.goods.model.Spu;
import com.fzm.mall.server.front.mq.MsgProducer;
import com.fzm.mall.server.front.order.model.po.TranDto;
import com.fzm.mall.server.front.user.mapper.UserMapper;
import com.fzm.mall.server.front.user.model.User;
import com.fzm.mall.server.front.util.ChainUtil;
import com.fzm.mall.server.front.util.CopyUtil;
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 java.math.BigDecimal;

/**
 * <p>
 * 用户资产-转赠 服务实现类
 * </p>
 *
 * @author fzm
 * @since 2021-08-17
 */
@Service
@Transactional
public class UserAssetTransferRecordServiceImpl extends ServiceImpl<UserAssetTransferRecordMapper, UserAssetTransferRecord> implements IUserAssetTransferRecordService {

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

    @Autowired
    private UserAssetMapper userAssetMapper;

    @Autowired
    private UserAssetTransferRecordMapper recordMapper;

    @Autowired
    private ChainUtil chainUtil;

    @Autowired
    private MsgProducer producer;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private GoodSpuMapper goodSpuMapper;

    @Override
    public void save(UserAssetTransferRecordSaveDTO saveDTO) {
        UserAssetTransferRecord record = CopyUtil.to(saveDTO, UserAssetTransferRecord.class);
        BigDecimal amount = new BigDecimal(record.getNum());
        checkTransferNum(record, amount);

        User user = userMapper.selectOne(new QueryWrapper<User>().eq("uid", record.getUid()));
        User userRecive = userMapper.selectOne(new QueryWrapper<User>().eq("address", record.getToAddress()));

        GoodSpu goodsSpu = goodSpuMapper.readByCoin(record.getCoin());
        goodsSpu.initTokenId();
        String hash = logTransferRecord(user, userRecive, amount, record.getCoin(), goodsSpu);

        record.init(userRecive.getUid(), inChain);
        recordMapper.insert(record);

        if (!inChain) {
            updateReciverAsset(record);
        } else {
            /**
             * 链上转账
             */
            transferInChain(user.getAddress(), record.getCoin(), record.getToAddress(), record.getNum(), record.getTransferRecordId(), hash, goodsSpu.getTokenId(), goodsSpu.getCoinType());
        }
    }

    private String logTransferRecord(User user, User userRecive, BigDecimal amount, String coin, GoodSpu goodSpu) {
        if (userRecive == null || user.getAddress().equals(userRecive.getAddress())) {
            throw new RuntimeException("addr_invalid");
        }

        boolean isSalesNFTCopy = goodSpu.getNftSalesType().equals(GoodSpu.NFT_SALES_TYPE_COPY);
        boolean commodityPassAuto = goodSpu.getCommodityPass().equals(Spu.COMMODITY_PASS_AUTO);
        boolean doEvmExec = true;
        /**
         * 商品为外部导入或者NFT销售类型为“原商品销售”，用外部NFT转账合约
         */
        if (commodityPassAuto || isSalesNFTCopy) {
            doEvmExec = false;
        }
        String hash;
        if (doEvmExec) {
            long[] tokenIds = new long[]{goodSpu.getTokenId()};
            long[] amounts = new long[]{amount.longValue()};
            hash = chainUtil.evmExec(tokenIds, amounts, user.getAddress(), userRecive.getAddress());
            goodSpu.setCoinType(TranDto.COIN_TYPE_NFT_OUT);
        } else {
            hash = chainUtil.evmTransfer(userRecive.getAddress(), user.getAddress(), goodSpu.getTokenId(), amount.longValue());
            goodSpu.setCoinType(TranDto.COIN_TYPE_NFT_IN);
        }
        return hash;
    }

    /**
     * 链上转让
     *
     * @param fromAddress
     * @param toAddress
     * @param num
     */
    private void transferInChain(String fromAddress, String coin, String toAddress, Integer num, String transferRecordId, String hash, Long tokenId, Integer coinType) {
        /**
         * 如果配置为不走链，则不操作链上资产
         */
        if (!inChain) {
            return;
        }
        TranDto tranDto = new TranDto();
        tranDto.setFromAddr(fromAddress);
        tranDto.setToAddr(toAddress);
        tranDto.setCoin(coin);
        //一个商品对应一个币，此处构造不存在精度丢失问题
        tranDto.setAmount(new BigDecimal(num.toString()));
        tranDto.setTableId(transferRecordId);
        tranDto.setTableType(TranDto.USER_ASSET_TRANSFER);
        tranDto.setHash(hash);
        tranDto.setTokenId(tokenId);
        tranDto.setCoinType(coinType);
        producer.sendTTL(JSON.toJSONString(tranDto), QueueTTLTypeEnum.MALL_USER_ASSET_TRANSFER_TTL, 5 * 1000);
    }

    private void checkTransferNum(UserAssetTransferRecord record, BigDecimal num) {
        QueryWrapper<UserAsset> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", record.getUid())
                .eq("coin", record.getCoin());
        UserAsset userAsset = userAssetMapper.selectOne(queryWrapper);
        boolean numError = userAsset.getAmount().compareTo(num) == -1;
        if (userAsset == null || numError) {
            throw new RuntimeException("user asset not enough, transferNum is invalid");
        }
        BigDecimal amount = userAsset.getAmount().subtract(num);
        userAsset.setAmount(amount);
        /**
         * 冻结转让的资产，链上操作成功后解冻
         */
        if (inChain) {
            BigDecimal frozen = userAsset.getFrozen().add(num);
            userAsset.setFrozen(frozen);
        }
        UpdateWrapper<UserAsset> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("uid", userAsset.getUid())
                .eq("coin", userAsset.getCoin())
                .eq("update_time", userAsset.getUpdateTime());
        int update = userAssetMapper.update(userAsset, updateWrapper);
        if (update == 0) {
            throw new RuntimeException("update fail");
        }
    }

    private void updateReciverAsset(UserAssetTransferRecord transferRecord) {
        BigDecimal reNumber = new BigDecimal(transferRecord.getNum());
        QueryWrapper<UserAsset> receiveWrapper = new QueryWrapper<UserAsset>().eq("uid", transferRecord.getToUid())
                .eq("coin", transferRecord.getCoin());
        UserAsset receiveAsset = userAssetMapper.selectOne(receiveWrapper);
        if (null == receiveAsset) {
            receiveAsset = new UserAsset();
            receiveAsset.setCoin(transferRecord.getCoin());
            receiveAsset.setUid(transferRecord.getToUid());
            receiveAsset.setType(1);
            receiveAsset.setAmount(reNumber);
            receiveAsset.setUid(transferRecord.getToUid());
            int reSave = userAssetMapper.insert(receiveAsset);
            if (reSave == 0) {
                throw new RuntimeException("save fail");
            }
        } else {
            BigDecimal amount = receiveAsset.getAmount().add(reNumber);
            receiveAsset.setAmount(amount);
            UpdateWrapper reAssetUpdateWrapper = new UpdateWrapper<UserAsset>().eq("uid", receiveAsset.getUid())
                    .eq("coin", receiveAsset.getCoin())
                    .eq("update_time", receiveAsset.getUpdateTime());
            int reUpdate = userAssetMapper.update(receiveAsset, reAssetUpdateWrapper);
            if (reUpdate == 0) {
                throw new RuntimeException("update fail");
            }
        }
    }

    @Override
    public UserAssetTransferRecordVO read(String transferRecordId) {
        QueryWrapper<UserAssetTransferRecord> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("transfer_record_id", transferRecordId);
        UserAssetTransferRecord record = recordMapper.selectOne(queryWrapper);
        return CopyUtil.to(record, UserAssetTransferRecordVO.class);
    }

    @Override
    public IPage<UserAssetTransferRecordVO> page(Page<UserAssetTransferRecordVO> page, String uid, String goodsId, String skuId) {
        return recordMapper.page(page, uid, goodsId, skuId, UserAssetTransferRecord.RECORD_STATUS_SUCCESS);
    }
}
