package com.fzm.mall.server.admin.order.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.core.toolkit.SystemClock;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fzm.mall.server.admin.block_chain.util.ChainUtil;
import com.fzm.mall.server.admin.config.Redis;
import com.fzm.mall.server.admin.constant.MallResponseError;
import com.fzm.mall.server.admin.distribution.mapper.ParcelCenterMapper;
import com.fzm.mall.server.admin.distribution.service.IExpressService;
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.SpuMapper;
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.DeliveryDTO;
import com.fzm.mall.server.admin.order.entity.dto.TranDto;
import com.fzm.mall.server.admin.order.mapper.DeliveryMapper;
import com.fzm.mall.server.admin.order.model.Config;
import com.fzm.mall.server.admin.order.model.Delivery;
import com.fzm.mall.server.admin.order.model.DeliveryLog;
import com.fzm.mall.server.admin.order.service.IDeliveryLogService;
import com.fzm.mall.server.admin.order.service.IDeliveryRefundService;
import com.fzm.mall.server.admin.order.service.IDeliveryService;
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.CopyUtil;
import lombok.RequiredArgsConstructor;
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;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 用户提货表 服务实现类
 * </p>
 *
 * @author wulixian
 * @since 2021-03-18
 */
@Service
@Transactional
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> implements IDeliveryService {

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

    private final DeliveryMapper deliveryMapper;
    private final ParcelCenterMapper parcelCenterMapper;
    private final IDeliveryLogService deliveryLogService;
    private final IAssetService assetService;
    private final IExpressService expressService;
    private final Redis redis;
    private final MsgProducer producer;
    private final IDeliveryRefundService deliveryRefundService;
    private final IUserService userService;
    private final IMerchantService merchantService;
    private final ChainUtil chainUtil;
    private final SpuMapper spuMapper;

    @Override
    public void send(String adminId, String orderDeliveryId, String expressType, String expressNo, Long updateTime) {

        /**
         * 提货单操作记录
         */
        DeliveryLog log = new DeliveryLog(orderDeliveryId, Delivery.DELIVERY_STATE_TO_SEND, DeliveryLog.DELIVERY_SENDED, adminId, null);
        deliveryLogService.save(log);

        UpdateWrapper<Delivery> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("order_delivery_id", orderDeliveryId)
                .eq("update_time", updateTime);
        Delivery delivery = new Delivery();
        delivery.setExpressType(expressType);
        delivery.setExpressNo(expressNo);
        delivery.setDeliveryState(Delivery.DELIVERY_STATE_SENDED);
        delivery.setOrderDeliveryId(orderDeliveryId);
        delivery.setDeliveryTime(SystemClock.now());
        //发货时重置物流详情为null，避免前台生成提货订单是，物流信息为"[]"
        delivery.setExpressDetail(null);
        int update = deliveryMapper.update(delivery, updateWrapper);
        if (update == 0) {
            throw new MyException(MallResponseError.UPDATE_FAIL);
        }

        /**
         * 开启提货订单到期自动关单
         */
        String configStr = redis.getKey(Config.ORDER_CONFIG_REDIS_KEY);
        Config config = JSON.parseObject(configStr, Config.class);
        //单位：天 -》转为秒
        Long goodsReceivedLimit = config.getGoodsReceivedLimit() * 24 * 60 * 60 * 1000;
        Map<String, Object> map = new HashMap<>();
        map.put("orderDeliveryId", delivery.getOrderDeliveryId());
        producer.sendTTL(map, QueueTTLTypeEnum.ORDER_DELIVERY_TTL, goodsReceivedLimit);
    }

    @Override
    public void examine(String adminId, String orderDeliveryId, String addressId, Boolean isApproved, String remark, Long updateTime) {
        String operation = DeliveryLog.DELIVERY_EXAMINE_FAILED;
        Integer delliveryState = Delivery.DELIVERY_STATE_REFUSED;
        if (isApproved) {
            operation = DeliveryLog.DELIVERY_EXAMINE_SUCCESS;
            delliveryState = Delivery.DELIVERY_STATE_RETURN_TO_BE_DESIGNED;
        }

        UpdateWrapper<Delivery> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("order_delivery_id", orderDeliveryId)
                .eq("update_time", updateTime)
                .set("delivery_state", delliveryState);
        int update = deliveryMapper.update(new Delivery(), updateWrapper);
        if (update == 0) {
            throw new MyException(MallResponseError.UPDATE_FAIL);
        }

        deliveryRefundService.refundExamine(orderDeliveryId, isApproved, remark, addressId);
        /**
         * 提货单操作记录
         */
        DeliveryLog log = new DeliveryLog(orderDeliveryId, Delivery.DELIVERY_STATE_TO_REFUND, operation, adminId, remark);
        deliveryLogService.save(log);
    }

    @Override
    public DeliveryDTO read(String orderDeliveryId) {
        Delivery delivery = deliveryMapper.read(orderDeliveryId);
        delivery.listExpressDetail(expressService);
        delivery.listGoods(parcelCenterMapper, orderDeliveryId);
        delivery.listLogs(deliveryLogService, orderDeliveryId);
        delivery.listReturnImg();

        return CopyUtil.to(delivery, DeliveryDTO.class);
    }

    @Override
    public IPage<DeliveryDTO> page(Page<DeliveryDTO> page, String uid, String orderDeliveryId, String gid, Integer deliveryState, String merchantId) {
        IPage<DeliveryDTO> result = deliveryMapper.list(page, uid, orderDeliveryId, gid, deliveryState, merchantId);
        return result;
    }

    @Override
    public void returnReceiptExamine(String adminId, String orderDeliveryId, Boolean isApproved, String remark, Long updateTime) {
        String operation = DeliveryLog.DELIVERY_RETURN_RECEIPT_EXAMINE_FAILED;
        Integer delliveryState = Delivery.DELIVERY_STATE_CLOSED;
        if (isApproved) {
            operation = DeliveryLog.DELIVERY_RETURN_RECEIPT_EXAMINE_SUCCESS;
            delliveryState = Delivery.DELIVERY_STATE_REFUNDED;
            /**
             * 退还提货的币
             */
            refundCoin(orderDeliveryId);
        }

        UpdateWrapper<Delivery> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("order_delivery_id", orderDeliveryId)
                .eq("update_time", updateTime)
                .set("delivery_state", delliveryState);
        int update = deliveryMapper.update(new Delivery(), updateWrapper);
        if (update == 0) {
            throw new MyException(MallResponseError.UPDATE_FAIL);
        }

        deliveryRefundService.returnReceiptExamine(orderDeliveryId, isApproved, remark);

        /**
         * 提货单操作记录
         */
        DeliveryLog log = new DeliveryLog(orderDeliveryId, Delivery.DELIVERY_STATE_TO_REFUND, operation, adminId, remark);
        deliveryLogService.save(log);
    }

    /**
     * 提货单退货，返回币到用户资产
     */
    private void refundCoin(String orderDeliveryId) {
        QueryWrapper<Delivery> queryWrapper = new QueryWrapper<Delivery>().eq("order_delivery_id", orderDeliveryId)
                .eq("is_delete", false);
        Delivery delivery = deliveryMapper.selectOne(queryWrapper);

        QueryWrapper<Asset> wrapper = new QueryWrapper<Asset>().eq("uid", delivery.getUid())
                .eq("coin", delivery.getCoin());
        Asset asset = assetService.getOne(wrapper);
        BigDecimal number = new BigDecimal(delivery.getNumber());
        BigDecimal amount = asset.getAmount() == null ? BigDecimal.ZERO.add(number) : asset.getAmount().add(number);
        asset.setAmount(amount);
        BigDecimal frozen = asset.getFrozen();
        BigDecimal result = frozen.subtract(number);
        frozen = result.compareTo(BigDecimal.ZERO) > 0 ? result : BigDecimal.ZERO;
        asset.setFrozen(frozen);
        UpdateWrapper updateWrapper = new UpdateWrapper<Asset>().eq("uid", asset.getUid())
                .eq("coin", asset.getCoin())
                .eq("update_time", asset.getUpdateTime());
        boolean update = assetService.update(asset, updateWrapper);
        if (!update) {
            throw new MyException(MallResponseError.UPDATE_FAIL);
        }

        refundCoinOnChain(delivery);
    }

    /**
     * 调用链上方法将币还给用户 , 成功后，将order_delivery_refund的state改为5退款成功
     *
     * @param delivery
     */
    private void refundCoinOnChain(Delivery delivery) {
        //根据开关判断是否走链，默认不走链
        if (payInChain) {
            User user = userService.getOne(new QueryWrapper<User>().eq("uid", delivery.getUid()));
            Merchant merchant = merchantService.getOne(new QueryWrapper<Merchant>().eq("merchant_id", delivery.getMerchantId()));
            TranDto tranDto = new TranDto();
            tranDto.setFromAddr(merchant.getTokenIssueAddr());
            tranDto.setToAddr(user.getAddress());
            tranDto.setCoin(delivery.getCoin());
            //一个商品对应一个币，此处构造不存在精度丢失问题
            tranDto.setAmount(new BigDecimal(delivery.getNumber()));
            tranDto.setTableId(delivery.getOrderDeliveryId());
            tranDto.setTableType(TranDto.TABLE_TYPE_DELIVERY);
            //判断商品是否为NFT
            GoodsSpuVO goodsSpu = spuMapper.readByCoin(delivery.getCoin());
            goodsSpu.initTokenId();
            String hash;
            if (goodsSpu.getCommodityPass().equals(Spu.COMMODITY_PASS_OUT)) {
                long[] tokenIds = new long[]{goodsSpu.getTokenId()};
                long[] amounts = new long[]{tranDto.getAmount().longValue()};
                hash = chainUtil.evmExec(tokenIds, amounts, tranDto.getFromAddr(), tranDto.getToAddr());
                tranDto.setCoinType(TranDto.COIN_TYPE_NFT_OUT);
            } else {
                hash = chainUtil.evmTransfer(tranDto.getToAddr(), tranDto.getFromAddr(), goodsSpu.getTokenId(), 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);
        }
    }
}
