package com.fzm.mall.server.front.mq.consumer;

import cn.fzm.chain.simplesdk.constant.TxStatusEnum;
import cn.fzm.chain.simplesdk.model.TxResult;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fzm.mall.server.front.asset.mapper.UserAssetRecordMapper;
import com.fzm.mall.server.front.asset.model.UserAsset;
import com.fzm.mall.server.front.asset.model.UserAssetRecord;
import com.fzm.mall.server.front.asset.model.UserAssetTransferRecord;
import com.fzm.mall.server.front.asset.service.IUserAssetService;
import com.fzm.mall.server.front.asset.service.IUserAssetTransferRecordService;
import com.fzm.mall.server.front.config.Redis;
import com.fzm.mall.server.front.constant.MALLGlobalConfig;
import com.fzm.mall.server.front.enums.QueueTTLTypeEnum;
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 io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

/**
 * 提币确认
 *
 * @author lgang
 * @date 创建时间：2018年12月13日 下午3:01:41
 */
@Slf4j
@Component
public class UserAssetTransferConsumer {


    @Autowired
    private ChainUtil chainUtil;

    @Autowired
    private MsgProducer msgProducer;

    @Autowired
    private IUserAssetTransferRecordService transferRecordService;

    @Autowired
    private IUserAssetService userAssetService;

    @Autowired
    private Redis redis;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserAssetRecordMapper userAssetRecordMapper;

    @RabbitListener(queues = MALLGlobalConfig.projectMq + ".user.asset.transfer.queue")
    public void receiver(String message) {
        try {
            if (StringUtil.isNullOrEmpty(message)) {
                return;
            }
            TranDto tranDto = JSON.parseObject(message, TranDto.class);
            if (tranDto == null) {
                log.error("UserAssetTransferConsumer :" + message + "-tranDto == null");
                return;
            }
            TxResult txResult = chainUtil.confirmTxWithTxHash(tranDto.getHash());
            TxStatusEnum txStatusEnum = txResult.getStatus();
            System.out.println(txResult.getErrMsg());
            switch (txStatusEnum) {
                case SUCCESS:
                    commit(tranDto, true);
                    break;
                case FAIL:
                    rollBack(tranDto);
                    break;
                default:
                    resend(tranDto);
                    break;
            }

        } catch (Exception e) {
            StackTraceElement[] stackTrace = e.getStackTrace();
            StringBuilder sb = new StringBuilder();
            for (StackTraceElement stackTraceElement : stackTrace) {
                sb.append(stackTraceElement.getFileName()).append("*").append(stackTraceElement.getLineNumber()).append("*");
            }
            log.error("UserAssetTransferConsumer-message:" + message + "-e" + sb.toString());
            msgProducer.sendTTL(message, QueueTTLTypeEnum.MALL_USER_ASSET_TRANSFER_TTL,
                    5 * 1000);
        }

    }

    @Transactional
    private void commit(TranDto tranDto, boolean successed) {
        switch (tranDto.getTableType()) {
            case 1:
                updateTransfer(tranDto, successed);
                break;
            default:
                //不抛异常,日志记录
                log.error("UserAssetTransferConsumer-tranDto type error: " + tranDto.getTableType());
                break;
        }
    }

    private void updateTransfer(TranDto tranDto, boolean successed) {

        QueryWrapper<UserAssetTransferRecord> recordQueryWrapper = new QueryWrapper<>();
        recordQueryWrapper.eq("transfer_record_id", tranDto.getTableId());

        UserAssetTransferRecord transferRecord = transferRecordService.getOne(recordQueryWrapper);
        if (transferRecord == null) {
            log.error("UserAssetTransferConsumer-transferRecord query fail, table_id : " + tranDto.getTableId());
            return;
        }
        if (successed) {
            transferRecord.setStatus(UserAssetTransferRecord.RECORD_STATUS_SUCCESS);
        } else {
            transferRecord.setStatus(UserAssetTransferRecord.RECORD_STATUS_FAIL);
        }
        UpdateWrapper<UserAssetTransferRecord> recordUpdateWrapper = new UpdateWrapper<>();
        recordUpdateWrapper.eq("transfer_record_id", tranDto.getTableId());
        transferRecordService.update(transferRecord, recordUpdateWrapper);

        /**
         * 成功：解冻转让方的资产给接收方
         * 失败：解冻转让方的资产给自己
         */
        updateTransferAsset(transferRecord, successed);

        /**
         * 成功：增加接收方的资产
         * 失败：不做任何操作
         */
        updateReciverAsset(transferRecord, successed, tranDto.getGoodsCoin());

        /**
         * 转赠流水记录
         */
        logTransferRecord(transferRecord, successed, tranDto.getHash());
    }

    @Transactional
    private void rollBack(TranDto tranDto) {
        log.error("user asset transfer fail,hash:" + tranDto.getHash());
        if (checkNum(tranDto)) {
            return;
        }
        String hash = getHash(tranDto);
        tranDto.setHash(hash);
        msgProducer.sendTTL(JSON.toJSONString(tranDto), QueueTTLTypeEnum.MALL_USER_ASSET_TRANSFER_TTL,
                5 * 1000);
    }

    private void resend(TranDto tranDto) {
        log.error("user asset transfer queue resend:" + tranDto);
        if (checkNum(tranDto)) {
            return;
        }
        if (StringUtil.isNullOrEmpty(tranDto.getHash())) {
            String hash = getHash(tranDto);
            tranDto.setHash(hash);
        }
        msgProducer.sendTTL(JSON.toJSONString(tranDto), QueueTTLTypeEnum.MALL_USER_ASSET_TRANSFER_TTL,
                5 * 1000);
    }

    private String getHash(TranDto tranDto) {
        String hash;
        if (tranDto.getCoinType().equals(TranDto.COIN_TYPE_NFT_OUT)) {
            long[] tokenIds = new long[]{tranDto.getTokenId()};
            long[] amounts = new long[]{tranDto.getAmount().longValue()};
            hash = chainUtil.evmExec(tokenIds, amounts, tranDto.getFromAddr(), tranDto.getToAddr());
        } else {
            hash = chainUtil.evmTransfer(tranDto.getToAddr(), tranDto.getFromAddr(), tranDto.getTokenId(), tranDto.getAmount().longValue());

        }
        return hash;
    }

    private void updateTransferAsset(UserAssetTransferRecord transferRecord, boolean successed) {
        /**
         * 成功：解冻转让方的资产
         * 失败：将转让方冻结的资产回归
         */
        QueryWrapper<UserAsset> wrapper = new QueryWrapper<UserAsset>().eq("uid", transferRecord.getUid())
                .eq("coin", transferRecord.getCoin());
        UserAsset asset = userAssetService.getOne(wrapper);
        if (null == asset) {
            log.error("query asset fail, uid:" + transferRecord.getUid() + "coin:" + transferRecord.getCoin());
            return;
        }
        BigDecimal number = new BigDecimal(transferRecord.getNum());
        BigDecimal frozen = asset.getFrozen();
        BigDecimal result = frozen.subtract(number);
        frozen = result.compareTo(BigDecimal.ZERO) > 0 ? result : BigDecimal.ZERO;
        asset.setFrozen(frozen);
        if (!successed) {
            BigDecimal amount = asset.getAmount();
            asset.setAmount(amount.add(number));
        }
        UpdateWrapper assetUpdateWrapper = new UpdateWrapper<UserAsset>().eq("uid", asset.getUid())
                .eq("coin", asset.getCoin())
                .eq("update_time", asset.getUpdateTime());
        boolean update = userAssetService.update(asset, assetUpdateWrapper);
        if (!update) {
            log.error("update asset fail, uid:" + asset.getUid() + "coin:" + asset.getCoin() + "update_time:" + asset.getUpdateTime());
        }
    }

    private void updateReciverAsset(UserAssetTransferRecord transferRecord, boolean successed, String goodsCoin) {
        if (!successed) {
            return;
        }
        BigDecimal reNumber = new BigDecimal(transferRecord.getNum());
        QueryWrapper<UserAsset> receiveWrapper = new QueryWrapper<UserAsset>().eq("uid", transferRecord.getToUid())
                .eq("coin", transferRecord.getCoin());
        UserAsset receiveAsset = userAssetService.getOne(receiveWrapper);
        if (null == receiveAsset) {
            log.warn("query asset fail, uid:" + transferRecord.getToUid() + "coin:" + transferRecord.getCoin());
            receiveAsset = new UserAsset();
            receiveAsset.setCoin(transferRecord.getCoin());
            receiveAsset.setUid(transferRecord.getToUid());
            receiveAsset.setType(1);
            receiveAsset.setAmount(reNumber);
            receiveAsset.setGoodsCoin(goodsCoin);
            boolean reSave = userAssetService.save(receiveAsset);
            if (!reSave) {
                log.error("save asset fail, uid:" + receiveAsset.getUid() + "coin:" + receiveAsset.getCoin());
            }
        } 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());
            boolean reUpdate = userAssetService.update(receiveAsset, reAssetUpdateWrapper);
            if (!reUpdate) {
                log.error("update asset fail, uid:" + receiveAsset.getUid() + "coin:" + receiveAsset.getCoin() + "update_time:" + receiveAsset.getUpdateTime());
            }
        }
    }

    private void logTransferRecord(UserAssetTransferRecord record, boolean successed, String hash) {

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

        if (null == user) {
            log.error("query user fail, uid:" + record.getUid());
            return;
        }

        if (null == userRecive) {
            log.error("query user recive fail, address:" + record.getToAddress());
            return;
        }

        BigDecimal reNumber = new BigDecimal(record.getNum());
        Integer stauts = UserAssetRecord.STATUS_FAIL;
        if (successed) {
            stauts = UserAssetRecord.STATUS_SUCCESS;
        }
        UserAssetRecord userAssetRecord = new UserAssetRecord();
        userAssetRecord.init(user.getUid(), record.getCoin(), 21, reNumber, stauts,
                user.getAddress(), hash, "转赠方扣除用户资产");
        userAssetRecordMapper.insert(userAssetRecord);

        /**
         * 生成资产变更记录-接收方
         */
        UserAssetRecord userReAssetRecord = new UserAssetRecord();
        userReAssetRecord.init(userRecive.getUid(), record.getCoin(), 20, reNumber, stauts,
                user.getAddress(), hash, "接收方增加用户资产");
        userAssetRecordMapper.insert(userReAssetRecord);
    }

    private boolean checkNum(TranDto tranDto) {
        /**
         * 失败重试3次
         */
        String redisKey = MALLGlobalConfig.PJ_SUFFIX + "_" + tranDto.getTableId();
        String countStr = redis.getKey(redisKey);
        if (countStr == null) {
            redis.setKey(redisKey, "1", 600000);
        } else if (Integer.valueOf(countStr) < 3) {
            Integer count = Integer.valueOf(countStr);
            count++;
            redis.setKey(redisKey, count.toString(), 600000);
        } else {
            //失败重试次数满了，操作失败
            commit(tranDto, false);
            return true;
        }
        return false;
    }
}
