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

import cn.fzm.chain.simplesdk.client.MainChainClient;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fzm.mall.server.front.asset.model.UserAsset;
import com.fzm.mall.server.front.asset.mapper.UserAssetMapper;
import com.fzm.mall.server.front.asset.model.UserAssetRecord;
import com.fzm.mall.server.front.asset.model.dto.AssetTranDTO;
import com.fzm.mall.server.front.asset.model.dto.coin.FrozenCoinDTO;
import com.fzm.mall.server.front.asset.model.po.qo.CoinLogQO;
import com.fzm.mall.server.front.asset.model.po.qo.CoinQO;
import com.fzm.mall.server.front.asset.model.po.qo.OrderQO;
import com.fzm.mall.server.front.asset.model.vo.asset.UserAssetVo;
import com.fzm.mall.server.front.asset.model.vo.coin.CoinLogVO;
import com.fzm.mall.server.front.asset.model.vo.coin.UserCoinVO;
import com.fzm.mall.server.front.asset.service.IUserAssetRecordService;
import com.fzm.mall.server.front.asset.service.IUserAssetService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fzm.mall.server.front.base.PageVO;
import com.fzm.mall.server.front.constant.StateConst;
import com.fzm.mall.server.front.enums.CoinNameEnum;
import com.fzm.mall.server.front.enums.QueueTTLTypeEnum;
import com.fzm.mall.server.front.goods.model.vo.PageVo;
import com.fzm.mall.server.front.goods.model.vo.SkuVo;
import com.fzm.mall.server.front.goods.service.ISkuService;
import com.fzm.mall.server.front.mq.MsgProducer;
import com.fzm.mall.server.front.order.model.po.TranDto;
import com.fzm.mall.server.front.order.model.vo.DeliveryVO;
import com.fzm.mall.server.front.system.model.SysConfig;
import com.fzm.mall.server.front.system.service.ISysConfigService;
import com.fzm.mall.server.front.txtLog.mapper.LogTxCoinMapper;
import com.fzm.mall.server.front.txtLog.model.LogTxCoin;
import com.fzm.mall.server.front.user.model.User;
import com.fzm.mall.server.front.user.service.IUserService;
import com.fzm.mall.server.front.util.OrderUtil;
import com.fzm.mall.server.front.util.TimeUtil;
import io.netty.util.internal.StringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author tdz
 * @date 2021/02/22 11:26
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserAssetServiceImpl extends ServiceImpl<UserAssetMapper, UserAsset> implements IUserAssetService {

    private final UserAssetMapper coinDao;
    private final LogTxCoinMapper txCoinDao;
    private final IUserService userService;
    private final MainChainClient mainChainClient;
    private final MsgProducer producer;
    private final IUserAssetRecordService userAssetRecordService;
    private final ISkuService skuService;
    private final ISysConfigService sysConfigService;

    /**
     * 分页查询用户资产
     */
    @Override
    public PageVO<UserAsset> pageAsset(final UserAssetVo userAssetVo) {
        Integer type = userAssetVo.getType();
        String uid = userAssetVo.getUid();
        QueryWrapper<UserAsset> qw = new QueryWrapper<>();
        qw.eq("type", type);
        qw.eq("uid", uid);
        String keyWord = userAssetVo.getKeyWord();
        List<UserAsset> list = new ArrayList<>();
        if (type == 1 && !StringUtil.isNullOrEmpty(keyWord)) {
            //模糊查询库存
            list = coinDao.searchAssetLike(uid, type, keyWord);
        }else{
            list = list(qw);
        }
        for (UserAsset i : list) {
            BigDecimal amount = i.getAmount();
            i.setAmount(amount.setScale(2, BigDecimal.ROUND_DOWN));
            i.setFrozen(i.getFrozen().setScale(2, BigDecimal.ROUND_DOWN));
            if(StringUtil.isNullOrEmpty(i.getCoin())){
                continue;
            }
            SkuVo skuVo = skuService.querySkuByCoin(i.getCoin());
            if (skuVo != null) {
                BigDecimal totalPrice = amount.multiply(skuVo.getOriginalPrice()).setScale(2, BigDecimal.ROUND_DOWN);
                i.setTotalPrice(totalPrice);
                i.setSkuVo(skuVo);
            }
        }
        PageVO<UserAsset> pageVO = new PageVO<>();
        pageVO.init(Integer.valueOf(userAssetVo.getPage()), Integer.valueOf(userAssetVo.getSize()), list.size(), list);
        return pageVO;
    }

    /**
     * 查询单个用户资产
     */
    @Override
    public UserAssetVo getOwnAsset(String uid, String coin) {
        QueryWrapper<UserAsset> qw = new QueryWrapper<>();
        qw.eq("uid", uid);
        qw.eq("coin", coin);
        UserAsset asset = getOne(qw);
        UserAssetVo userAssetVo = new UserAssetVo();
        if (asset != null) {
            asset.setAmount(asset.getAmount().setScale(2, BigDecimal.ROUND_DOWN));
            asset.setFrozen(asset.getFrozen().setScale(2, BigDecimal.ROUND_DOWN));
            BeanUtils.copyProperties(asset, userAssetVo);
            SysConfig sysConfig = sysConfigService.getByKey("coin_logo");
            if (sysConfig != null) {
                userAssetVo.setLogo(sysConfig.getDictValue());
            }
        }
        return userAssetVo;
    }


    @Transactional
    public String mallAssetTrans(AssetTranDTO dto, User user) {
        Boolean isChain = dto.getIsChain();
        String uid = user.getUid();
        String fromAddr = user.getAddress();
        String toAddr = dto.getOtherAddress();
        User other = userService.getByAddress(toAddr);
        if (other == null) {
            //地址不存在
            throw new RuntimeException("address_not_exist");
        }
        String otherUid = other.getUid();
        BigDecimal amount = dto.getAmount();
        String remarks = dto.getRemarks();
        String coin = dto.getCoin();
        //减少用户资产
        UserAssetVo ownAsset = getOwnAsset(uid, coin);
        ownAsset.setAmount(ownAsset.getAmount().subtract(amount));
        ownAsset.setFrozen(ownAsset.getFrozen().add(amount));
        UserAsset newAsset = new UserAsset();
        BeanUtils.copyProperties(ownAsset, newAsset);
        boolean b = saveOrUpdate(newAsset);
        if (!b) {
            //用户资产不足
            throw new RuntimeException("asset_not_enough");
        }
        //增加对方用户资产
        UserAssetVo otherAsset = getOwnAsset(otherUid, coin);
        if (otherAsset.getId() == null) {
            otherAsset.setUid(otherUid);
            otherAsset.setCoin(coin);
            otherAsset.setType(0);//0支付币
            otherAsset.setAmount(amount);
        } else {
            otherAsset.setAmount(otherAsset.getAmount().add(amount));
        }
        UserAsset newOtherAsset = new UserAsset();
        BeanUtils.copyProperties(otherAsset, newOtherAsset);
        saveOrUpdate(newOtherAsset);


        String hash = "";
        if (isChain) {
            hash = mainChainClient.mainTranToken(fromAddr, null, toAddr, amount, coin);
        }

        String flowId = OrderUtil.getFlowId();
        UserAssetRecord record = new UserAssetRecord();
        record.setAmount(amount);
        record.setUid(uid);
        record.setCoin(coin);
        record.setType(6);//数字资产转账-转出
        record.setHash(hash);
        record.setFlowId(flowId);
        record.setStatus(3);//支付中
        record.setOtherSide(toAddr);
        record.setNote(remarks);
        boolean saveReco = userAssetRecordService.save(record);
        if (!saveReco) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        UserAssetRecord recordOther = new UserAssetRecord();
        recordOther.setAmount(amount);
        recordOther.setUid(otherUid);
        recordOther.setCoin(coin);
        recordOther.setType(5);//数字资产转帐-转入
        recordOther.setHash(hash);
        recordOther.setFlowId(flowId);
        recordOther.setStatus(2);
        recordOther.setOtherSide(fromAddr);
        recordOther.setNote(remarks);
        saveReco = userAssetRecordService.save(recordOther);
        if (!saveReco) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        //轮询确认，异步执行;
        TranDto tranDto = new TranDto();
        tranDto.setHash(hash);
        tranDto.setAmount(amount);
        tranDto.setCoin(coin);
        tranDto.setFromAddr(fromAddr);
        tranDto.setToAddr(toAddr);
        tranDto.setId(record.getId());
        tranDto.setIsChain(isChain);

        //异步发行token
        producer.sendTTL(JSON.toJSONString(tranDto), QueueTTLTypeEnum.MALL_ASSET_TRAN_TTL, 5 * 1000);

        return "success";
    }

    /**
     * 查询是否有充足的指定币
     */
    @Override
    public boolean coinEnough(final String uid, final String isToken, final String coin, final String block,
                              final String amount) {
        Integer ok = coinDao.coinEnough(uid, Integer.valueOf(isToken), coin, Integer.valueOf(block), amount);
        return ok != null && ok > 0;
    }

    /**
     * 增加用户币
     */
    @Override
    public boolean addUserCoin(String uid, Integer isToken, String coin, Integer block, String amount, int channel,
                               String address) {
        return addCoin(uid, isToken, coin, block, amount, channel, address, true);
    }

    /**
     * 增加用户币不记录日志
     */
    @Override
    public boolean addUserCoinNoLog(String uid, Integer isToken, String coin, Integer block, String amount) {
        return addCoin(uid, isToken, coin, block, amount, 0, null, false);
    }

    private boolean addCoin(String uid, Integer isToken, String coin, Integer block, String amount, int channel,
                            String address, boolean isLog) {
        Integer ok = coinDao.coinExist(uid, isToken, coin, block);
        long timestamp = TimeUtil.timestamp();
        if (ok == null || ok < 1) {
            // 插入
            ok = coinDao.saveUserCoin(uid, isToken, coin, block, amount, timestamp);
        } else {
            // 更新
            ok = coinDao.updateAddCoin(uid, isToken, coin, block, amount, timestamp);
        }
        if (ok == null || ok != 1) {
            log.info("增加用户币失败：{} -> {} -> {}", uid, coin, amount);
            return false;

        }
        if (isLog) {
            LogTxCoin txCoin = new LogTxCoin();
            txCoin.setUid(uid);
            txCoin.setChannel(channel);
            txCoin.setType(StateConst.TX_COIN_IN);
            txCoin.setIsToken(isToken);
            txCoin.setBlock(block);
            txCoin.setCoin(coin);
            txCoin.setAddress(address);
            txCoin.setAmount(new BigDecimal(amount));
            txCoinDao.insert(txCoin);
        }
        return true;
    }

    /**
     * 减少用户币
     */
    @Override
    public boolean minusUserCoin(String uid, Integer isToken, String coin, Integer block, String amount, int channel,
                                 String address) {
        return minusCoin(uid, isToken, coin, block, amount, channel, address, true);
    }

    /**
     * 减少用户币不记录日志
     */
    @Override
    public boolean minusUserCoinNoLog(String uid, Integer isToken, String coin, Integer block, String amount) {
        return minusCoin(uid, isToken, coin, block, amount, 0, null, false);
    }

    private boolean minusCoin(String uid, Integer isToken, String coin, Integer block, String amount, int channel,
                              String address, boolean isLog) {
        long timestamp = TimeUtil.timestamp();
        Integer ok = coinDao.updateMinusCoin(uid, isToken, coin, block, amount, timestamp);
        if (ok == null || ok != 1) {
            log.info("减少用户币失败：{} -> {} -> {}", uid, coin, amount);
            return false;
        }
        if (isLog) {
            LogTxCoin txCoin = new LogTxCoin();
            txCoin.setUid(uid);
            txCoin.setChannel(channel);
            txCoin.setType(StateConst.TX_COIN_IN);
            txCoin.setIsToken(isToken);
            txCoin.setBlock(block);
            txCoin.setCoin(coin);
            txCoin.setAddress(address);
            txCoin.setAmount(new BigDecimal(amount));
            txCoinDao.insert(txCoin);
        }
        return true;
    }

    /**
     * 冻结用户币
     */
    @Override
    public boolean frozenUserCoin(String uid, Integer isToken, String coin, Integer block, String amount) {
        long timestamp = TimeUtil.timestamp();
        Integer ok = coinDao.frozen(uid, isToken, coin, block, amount, timestamp);
        if (ok != null && ok == 1) {
            return true;
        }
        log.info("冻结用户币失败：{} -> {} -> {}", uid, coin, amount);
        return false;
    }

    /**
     * 解冻用户币
     */
    @Override
    public boolean unfrozenUserCoin(String uid, Integer isToken, String coin, Integer block, String amount) {
        long timestamp = TimeUtil.timestamp();
        Integer ok = coinDao.unfrozen(uid, isToken, coin, block, amount, timestamp);
        if (ok != null && ok == 1) {
            return true;
        }
        log.info("解冻用户币失败：{} -> {} -> {}", uid, coin, amount);
        return false;
    }

    /**
     * 减少用户冻结币
     */
    @Override
    public boolean minusUserfrozenCoin(String uid, Integer isToken, String coin, Integer block, String amount,
                                       int channel, String address) {
        long timestamp = TimeUtil.timestamp();
        Integer ok = coinDao.updateMinusCoinfrozen(uid, isToken, coin, block, amount, timestamp);
        if (ok != null && ok == 1) {
            LogTxCoin txCoin = new LogTxCoin();
            txCoin.setUid(uid);
            txCoin.setChannel(channel);
            txCoin.setType(StateConst.TX_COIN_IN);
            txCoin.setIsToken(isToken);
            txCoin.setBlock(block);
            txCoin.setCoin(coin);
            txCoin.setAddress(address);
            txCoin.setAmount(new BigDecimal(amount));
            txCoinDao.insert(txCoin);
            return true;
        }
        log.info("减少用户冻结币失败：{} -> {} -> {}", uid, coin, amount);
        return false;
    }

    @Override
    public boolean addUserfrozenCoin(String uid, Integer isToken, String coin, Integer block, String amount,
                                     int channel, String address) {
        long timestamp = TimeUtil.timestamp();
        Integer ok = coinDao.updateAddCoinfrozen(uid, isToken, coin, block, amount, timestamp);
        if (ok != null && ok == 1) {
            LogTxCoin txCoin = new LogTxCoin();
            txCoin.setUid(uid);
            txCoin.setChannel(channel);
            txCoin.setType(StateConst.TX_COIN_IN);
            txCoin.setIsToken(isToken);
            txCoin.setBlock(block);
            txCoin.setCoin(coin);
            txCoin.setAddress(address);
            txCoin.setAmount(new BigDecimal(amount));
            txCoinDao.insert(txCoin);
            return true;
        }
        log.info("增加用户冻结币失败：{} -> {} -> {}", uid, coin, amount);
        return false;
    }

    /**
     * 查询用户指定资产
     */
    @Override
    public UserAsset coin(final String uid, final String isToken, final String coin, final String block) {
        return coinDao.coin(uid, isToken, coin, block);
    }

    /**
     * 分页查询用户账户资产
     */
    @Override
    public PageVO<UserCoinVO> pageCoin(final CoinQO coinQO) {
        List<UserAsset> assets = coinDao.listCoin(coinQO);
        List<UserCoinVO> listCoin = new ArrayList<>();
        BeanUtils.copyProperties(assets, listCoin);
        Integer totalSize = coinDao.countCoin(coinQO);
        PageVO<UserCoinVO> pageVO = new PageVO<>();
        pageVO.init(coinQO.getPage(), coinQO.getSize(), totalSize, listCoin);
        return pageVO;
    }

    /**
     * 分页查询用户充提币记录
     */
    @Override
    public PageVO<CoinLogVO> pageLog(final CoinLogQO coinLogQO) {
        List<CoinLogVO> listLog = coinDao.listLog(coinLogQO);
        Integer totalSize = coinDao.countLog(coinLogQO);
        PageVO<CoinLogVO> pageVO = new PageVO<>();
        pageVO.init(coinLogQO.getPage(), coinLogQO.getSize(), totalSize, listLog);
        return pageVO;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchfrozenUserCoin(List<FrozenCoinDTO> frozenCoinList) {
        for (FrozenCoinDTO coinDto : frozenCoinList) {
            boolean frozenRes = frozenUserCoin(coinDto.getUid(), coinDto.getIsToken(), coinDto.getCoin(),
                    coinDto.getBlock(), coinDto.getAmount());
            if (!frozenRes) {
                log.info("批量总结用户积分失败，批量积分：{}, 失败积分:{}", frozenCoinList, coinDto);
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return false;
            }
        }
        return true;
    }

    @Override
    @Transactional
    public boolean unbatchfrozenUserCoin(List<FrozenCoinDTO> frozenCoinList) {
        for (FrozenCoinDTO coinDto : frozenCoinList) {
            boolean unfrozenRes = unfrozenUserCoin(coinDto.getUid(), coinDto.getIsToken(), coinDto.getCoin(),
                    coinDto.getBlock(), coinDto.getAmount());
            if (!unfrozenRes) {
                log.info("批量解冻用户积分失败，批量积分：{}, 失败积分:{}", frozenCoinList, coinDto);
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return false;
            }
        }
        return true;
    }


    @Override
    public List<UserAsset> coinList(String uid, List<String> coins) {
        if (coins != null && coins.size() > 0) {
            return coinDao.coinList(uid, coins);
        }
        return coinDao.userCoinList(uid);
    }


    @Override
    public int delAsset(String uid, BigDecimal unpaidAmount, String coinName) {
        return coinDao.delAsset(uid, unpaidAmount, coinName);
    }

    @Override
    public UserAsset getCoin(String uid, String coinName) {
        QueryWrapper<UserAsset> query = new QueryWrapper<>();
        query.eq("uid", uid);
        query.eq("coin", coinName);
        return getOne(query);
    }

    @Override
    public List<UserAsset> getList(String uid, PageVo pageVo) {
        int start = (pageVo.getPage() - 1) * pageVo.getSize();
        return coinDao.getList(uid, start, pageVo.getSize());
    }

    @Override
    public Integer getCount(String uid) {
        return coinDao.getCount(uid);
    }

    @Override
    public BigDecimal getAllNotMallCoin(String uid) {
        return coinDao.getAllNotMallCoin(uid);
    }

    @Override
    public void addCoin(Long id, BigDecimal amount) {
        coinDao.addCoin(id, amount);
    }

    @Override
    public int frozenUserAssets(String uid, String coin, Integer number) {
        return coinDao.frozenUserAssets(uid, coin, number);
    }

    @Override
    public int unfrozenUserAsset(String uid, Integer number, String coin) {
        return coinDao.unfrozenUserAsset(uid, number, coin);
    }

    @Override
    public void addUserfrozenAsset(Long id, BigDecimal amount) {
        coinDao.addUserfrozenAsset(id, amount);
    }

    @Override
    public UserAsset getAmountByUidAndCoinForUpdate(String uid, String string) {
        QueryWrapper<UserAsset> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", uid);
        queryWrapper.eq("coin", string);
        queryWrapper.last("for update");
        return getOne(queryWrapper);
    }

    @Override
    public int deleteFrozen(Long id, BigDecimal amount) {
        return coinDao.deleteFrozen(id, amount);
    }

    @Override
    public int addReward(String uid, String commentId, BigDecimal evaluationRefund) {
        UserAssetRecord userAssetRecord = userAssetRecordService.getByUidAndcommentId(uid, commentId);
        if (userAssetRecord == null) {
            UserAsset coin = getCoin(uid, CoinNameEnum.MALLCOIN.value());
            addCoin(coin.getId(), evaluationRefund);
            userAssetRecord = new UserAssetRecord();
            userAssetRecord.setAmount(evaluationRefund);
            userAssetRecord.setCoin(CoinNameEnum.MALLCOIN.value());
            userAssetRecord.setFlowId(OrderUtil.getFlowId());
            userAssetRecord.setUid(uid);
            userAssetRecord.setType(19);
            userAssetRecord.setStatus(2);
            userAssetRecord.setNote(commentId);
            userAssetRecord.setOtherSide("平台");
            userAssetRecordService.save(userAssetRecord);
            return 1;
        }
        return 0;
    }

    @Override
    public int deleteAmount(Long id, BigDecimal amount) {
        return coinDao.deleteAmount(id,amount);
    }
}
